Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
52.00% covered (warning)
52.00%
3232 / 6215
70.07% covered (success)
70.07%
103 / 147
CRAP
0.00% covered (danger)
0.00%
0 / 1
ApiController
51.94% covered (warning)
51.94%
3224 / 6207
70.07% covered (success)
70.07%
103 / 147
274120.97
0.00% covered (danger)
0.00%
0 / 1
 beforeFilter
0.00% covered (danger)
0.00%
0 / 52
0.00% covered (danger)
0.00%
0 / 1
2
 countUnreadSpotlightPost
100.00% covered (success)
100.00%
28 / 28
100.00% covered (success)
100.00%
1 / 1
1
 checkSession
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
6
 generatePeerID
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
2
 getEndTime
100.00% covered (success)
100.00%
24 / 24
100.00% covered (success)
100.00%
1 / 1
6
 getNowTime
39.23% covered (warning)
39.23%
71 / 181
0.00% covered (danger)
0.00%
0 / 1
437.95
 isSequenceOf
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
6
 checkForMaintenance
100.00% covered (success)
100.00%
32 / 32
100.00% covered (success)
100.00%
1 / 1
4
 checkOngoingMaintenance
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 getLessonStatus
0.00% covered (danger)
0.00%
0 / 208
0.00% covered (danger)
0.00%
0 / 1
4422
 saveAudioMemcached
100.00% covered (success)
100.00%
51 / 51
100.00% covered (success)
100.00%
1 / 1
7
 addTeacherStatusLog
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
3
 getProfile
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
30
 getHeader
0.00% covered (danger)
0.00%
0 / 48
0.00% covered (danger)
0.00%
0 / 1
72
 getMemo
0.00% covered (danger)
0.00%
0 / 23
0.00% covered (danger)
0.00%
0 / 1
156
 outputMemo
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 getTransfer
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
4
 setTransfer
100.00% covered (success)
100.00%
28 / 28
100.00% covered (success)
100.00%
1 / 1
5
 updateTransfer
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
7
 deleteTransfer
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
5
 triggerLessonNoteValidation
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
5
 saveTeacherLearnings
0.00% covered (danger)
0.00%
0 / 324
0.00% covered (danger)
0.00%
0 / 1
16256
 SendEditMessageSlack
0.00% covered (danger)
0.00%
0 / 35
0.00% covered (danger)
0.00%
0 / 1
12
 arrReqValIsOk
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
7
 saveTeacherMemo
100.00% covered (success)
100.00%
20 / 20
100.00% covered (success)
100.00%
1 / 1
12
 setMemo
0.00% covered (danger)
0.00%
0 / 29
0.00% covered (danger)
0.00%
0 / 1
210
 outputSetMemo
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 getAnnounceCount
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
1
 getRequestCount
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
1
 getNextReservationTime
96.43% covered (success)
96.43%
54 / 56
0.00% covered (danger)
0.00%
0 / 1
12
 checkIfMealBreakTime
97.44% covered (success)
97.44%
38 / 39
0.00% covered (danger)
0.00%
0 / 1
11
 setLessonMemo
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
72
 createTeacherStatusLog
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
4
 getUnviewedNotice
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getUnviewedRule
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 askHelp
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
6
 getBreaks
98.80% covered (success)
98.80%
82 / 83
0.00% covered (danger)
0.00%
0 / 1
20
 checkCanBreak
0.00% covered (danger)
0.00%
0 / 73
0.00% covered (danger)
0.00%
0 / 1
210
 checkCanBreakSlot
0.00% covered (danger)
0.00%
0 / 70
0.00% covered (danger)
0.00%
0 / 1
272
 countTeacherOnBreaks
100.00% covered (success)
100.00%
20 / 20
100.00% covered (success)
100.00%
1 / 1
1
 getReservedOrAvailableOnMealBreak
94.12% covered (success)
94.12%
16 / 17
0.00% covered (danger)
0.00%
0 / 1
3.00
 canLesson
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
4
 studentDisconnection
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
3
 setValidLesson
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
2
 countUnreadAppreciationMessage
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 countUnsentMessage
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
4
 getlastCallanStage
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
1 / 1
10
 callStartTeacherApi
0.00% covered (danger)
0.00%
0 / 311
0.00% covered (danger)
0.00%
0 / 1
13572
 saveLessonConnectionType
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
30
 loadPreviousChatMessages
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
4
 checkSlotHasReservation
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
5
 reviveSessionChecker
100.00% covered (success)
100.00%
62 / 62
100.00% covered (success)
100.00%
1 / 1
26
 syncScheduleFromAccounting
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 getUnlessonReservationAndLessonFinishTime
100.00% covered (success)
100.00%
42 / 42
100.00% covered (success)
100.00%
1 / 1
4
 finishUnlessonReservation
100.00% covered (success)
100.00%
63 / 63
100.00% covered (success)
100.00%
1 / 1
6
 getReservationAndCancelTime
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
6
 getRLSShowModalTime
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
4
 cancelReservation
100.00% covered (success)
100.00%
65 / 65
100.00% covered (success)
100.00%
1 / 1
12
 getStudentDisconnectionTime
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
5
 finishStatusChange
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
4
 saveImageLink
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
6
 get3MinutesBreakRemainingTime
0.00% covered (danger)
0.00%
0 / 102
0.00% covered (danger)
0.00%
0 / 1
420
 getImpendingAndOngoingReservation
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 deleteMemMinutesBreakRemainingTime
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
3
 saveMemo
100.00% covered (success)
100.00%
54 / 54
100.00% covered (success)
100.00%
1 / 1
32
 saveSelectedTextbook
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
8
 saveSelectedTextbookLessonOnAir
100.00% covered (success)
100.00%
31 / 31
100.00% covered (success)
100.00%
1 / 1
4
 saveSelectedTextbookLessonOnAirLogs
100.00% covered (success)
100.00%
31 / 31
100.00% covered (success)
100.00%
1 / 1
4
 updateMemo
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
7
 lessonPeriod
100.00% covered (success)
100.00%
57 / 57
100.00% covered (success)
100.00%
1 / 1
7
 checkTimezone
98.32% covered (success)
98.32%
117 / 119
0.00% covered (danger)
0.00%
0 / 1
20
 updateTeacherTimezone
100.00% covered (success)
100.00%
26 / 26
100.00% covered (success)
100.00%
1 / 1
6
 getServerAndLocalTime
100.00% covered (success)
100.00%
53 / 53
100.00% covered (success)
100.00%
1 / 1
4
 checkDaylightSavingTime
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 getOpenedSlot
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 deleteEqualTo5MinutesAndBelowModal
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
3
 updateCounselingAttendedFlg
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
2
 checkReservation
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
4
 sanitizeInputs
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
4
 getPopupList
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
3
 getTextbookCategoryId
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
4
 postSlack
100.00% covered (success)
100.00%
41 / 41
100.00% covered (success)
100.00%
1 / 1
6
 sendChivoxSlack
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
2
 saveAdminStaffEvaluation
100.00% covered (success)
100.00%
20 / 20
100.00% covered (success)
100.00%
1 / 1
4
 getAnnouncement
100.00% covered (success)
100.00%
52 / 52
100.00% covered (success)
100.00%
1 / 1
6
 getAnnouncementRead
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
4
 getLessonRequest
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 translateText
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
4
 checkTeacherEptSetting
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
4
 lessonOnAirRequestLessonTime
100.00% covered (success)
100.00%
136 / 136
100.00% covered (success)
100.00%
1 / 1
53
 getDateStatus
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
1 / 1
1
 checkFRSendSlack
0.00% covered (danger)
0.00%
0 / 162
0.00% covered (danger)
0.00%
0 / 1
156
 lessionUnitPrice
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
5
 studentYearlySpeakingTest
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
10
 uploadRecordedFile
0.00% covered (danger)
0.00%
0 / 109
0.00% covered (danger)
0.00%
0 / 1
992
 uploadRecordedFileChivox
0.00% covered (danger)
0.00%
0 / 350
0.00% covered (danger)
0.00%
0 / 1
9702
 saveConnectionSpeed
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
6
 disconnectionCancellationMemCached
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
5
 uploadAttachedFileToAWS
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
30
 downloadChatLogFiles
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
20
 uploadeCancelDeleteFile
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
4
 uploadRecordedFileMerge
0.00% covered (danger)
0.00%
0 / 244
0.00% covered (danger)
0.00%
0 / 1
3306
 uploadRecordedFileMergeAsync
0.00% covered (danger)
0.00%
0 / 233
0.00% covered (danger)
0.00%
0 / 1
3660
 getTimezoneUserAndTeacher
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
1
 uploadRecordedFileAsync
0.00% covered (danger)
0.00%
0 / 107
0.00% covered (danger)
0.00%
0 / 1
1122
 debugSocketLog
0.00% covered (danger)
0.00%
0 / 48
0.00% covered (danger)
0.00%
0 / 1
90
 logTeacherDebugError
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
 sendSlackDebugMessage
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
3
 saveSkywayDebug
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
6
 reCheckReservedLesson
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 virtualCameraAllow
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
4
 getTextbookEquivalentScores
100.00% covered (success)
100.00%
42 / 42
100.00% covered (success)
100.00%
1 / 1
11
 confirmSuddenLesson
100.00% covered (success)
100.00%
96 / 96
100.00% covered (success)
100.00%
1 / 1
17
 postEventCampaign
100.00% covered (success)
100.00%
34 / 34
100.00% covered (success)
100.00%
1 / 1
11
 getTeachinMaterialEngName
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
5
 teacherAudioDataMemCached
98.18% covered (success)
98.18%
54 / 55
0.00% covered (danger)
0.00%
0 / 1
11
 nextLessonReservation
100.00% covered (success)
100.00%
63 / 63
100.00% covered (success)
100.00%
1 / 1
12
 otherAudioDataMemCached
68.52% covered (success)
68.52%
37 / 54
0.00% covered (danger)
0.00%
0 / 1
18.27
 fcmWebNotification
100.00% covered (success)
100.00%
28 / 28
100.00% covered (success)
100.00%
1 / 1
4
 studentLeftLessonUpdate
100.00% covered (success)
100.00%
24 / 24
100.00% covered (success)
100.00%
1 / 1
8
 updateCurrentTutorialModalSteps
100.00% covered (success)
100.00%
28 / 28
100.00% covered (success)
100.00%
1 / 1
9
 getUnviewedWarning
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 showReservedLessonSpecialModal
94.44% covered (success)
94.44%
17 / 18
0.00% covered (danger)
0.00%
0 / 1
5.00
 getLearningKitCount
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
 dynamicModalData
0.00% covered (danger)
0.00%
0 / 61
0.00% covered (danger)
0.00%
0 / 1
72
 saveBookmark
100.00% covered (success)
100.00%
161 / 161
100.00% covered (success)
100.00%
1 / 1
29
 saveCallanStage
100.00% covered (success)
100.00%
80 / 80
100.00% covered (success)
100.00%
1 / 1
15
 fetchBookmark
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
6
 fetchBookmarkTextbook
100.00% covered (success)
100.00%
54 / 54
100.00% covered (success)
100.00%
1 / 1
5
 saveNewCallanLevelCheck
100.00% covered (success)
100.00%
173 / 173
100.00% covered (success)
100.00%
1 / 1
25
 getStageConnectID
100.00% covered (success)
100.00%
28 / 28
100.00% covered (success)
100.00%
1 / 1
3
 recordPlayedPhrase
100.00% covered (success)
100.00%
30 / 30
100.00% covered (success)
100.00%
1 / 1
5
 gameGetPlayersProfile
100.00% covered (success)
100.00%
35 / 35
100.00% covered (success)
100.00%
1 / 1
6
 saveGameResult
97.78% covered (success)
97.78%
44 / 45
0.00% covered (danger)
0.00%
0 / 1
8
 saveGameDataMemcache
99.00% covered (success)
99.00%
99 / 100
0.00% covered (danger)
0.00%
0 / 1
21
 gameDataMemcache
94.12% covered (success)
94.12%
16 / 17
0.00% covered (danger)
0.00%
0 / 1
3.00
 getCurrentChatHash
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
3
 googleCalendarCb
0.00% covered (danger)
0.00%
0 / 39
0.00% covered (danger)
0.00%
0 / 1
42
 googleCalendarFlagUpdate
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
42
 unlinkGoogleCalendar
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
6
 isUserChocottoCamp
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 getUploadRecordedFileAsync
46.00% covered (warning)
46.00%
23 / 50
0.00% covered (danger)
0.00%
0 / 1
44.86
 googleConvertedFileUpload
0.00% covered (danger)
0.00%
0 / 26
0.00% covered (danger)
0.00%
0 / 1
56
 googleConvertedSpeech
0.00% covered (danger)
0.00%
0 / 33
0.00% covered (danger)
0.00%
0 / 1
72
 getLabelTranslations
100.00% covered (success)
100.00%
26 / 26
100.00% covered (success)
100.00%
1 / 1
1
 sendSlackStreamCheckError
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
3
 lessonChatHashChecker
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2App::uses('AwsFileServer', 'Lib');
3App::uses('AppController', 'Controller');
4App::uses('LessonMessageController', 'Controller');
5App::uses('firebaseCloudMsg', 'Lib');
6App::uses('GoogleCalendar','Lib');
7App::uses('GlobalTextbookTable','Model');
8App::uses('GlobalTextbookCategoryTable','Model');
9App::uses('GlobalTextbookSubcategoryTable','Model');
10
11class ApiController extends AppController{
12
13    public $uses = array(
14        'LessonOnair',
15        'Post',
16        'UsersClass',
17        'LessonText',
18        'User',
19        'LessonUserOnair',
20        'Teacher',
21        'LessonTransfer',
22        'LessonSchedule',
23        'LessonScheduleCancel',
24        'LessonMemo',
25        'TeacherStatusLog',
26        'UsersLessonLearning',
27        'Notification',
28        'TeacherStatus',
29        'TeacherBreaktimeDetail',
30        'LessonOnair',
31        'LessonOnairsLog',
32        'ChatHistory',
33        'ShiftWorkOn',
34        'BreakLimit',
35        'TeacherMonitorImages',
36        'TeacherRankCoin',
37        'Timezone',
38        'Workstation',
39        'LessonPopup',
40        'TextbookConnect',
41        'ProhibitedWord',
42        'AdminStaffEvaluation',
43        'TeacherAnnouncement',
44        'TeacherAnnouncementsRead',
45        'LessonRequest',
46        'ShiftWorkMealBreak',
47        'FaceRegistration',
48        'FaceRecognition',
49        'SettingOption',
50        'LessonConnectionSpeedLog',
51        'AnnounceTeacherGroup',
52        'TeacherGroupLink',
53        'UsersChivoxCountQuery',
54        'Nationality',
55        'OfficeMessageIncentive',
56        'FamilyPlanList',
57        'TeacherCoinBox',
58        'Payment',
59        'LessonOnairsLog',
60        'TeachersDebugSkyway',
61        'HomeBasedReserveCoinData',
62        'HomeBasedFinalSalaryData',
63        'HomeBasedRankBasicAmount',
64        'HomeBasedTeacherRankLog',
65        'Warning',
66        'TeacherTimeSetting',
67        'UsersExtend',
68        'TrainingData',
69        'TeacherBadge',
70        'TeacherAvatarCameraAllowed',
71        'CharacterSettings',
72        'LessonBookmark',
73        'LessonBookmarkHistory',
74        'CallanBookmarkOption',
75        'UserNewLessonCallanProgress',
76        'TeacherAccessToken',
77        'UserDiscountOptionsTerm',
78        'LessonSchedulesExtend',
79        'NCContentList',
80        'TeacherSpotlightReaction',
81        'TeacherSpotlightView',
82        'TeacherSpotlight',
83        'UsersDetail'
84    );
85
86    public $components = array('Session');
87    public $homeBase = NULL;
88    public function beforeFilter() {
89        parent::beforeFilter();
90        //instantiate slack
91        $this->mySlack = new mySlack();
92        $this->Auth->allow(
93            'checkSession',
94            'getNowTime',
95            'getLessonStatus',
96            'loadPreviousChatMessages',
97            'callStartTeacherApi',
98            'reviveSessionChecker',
99            'studentDisconnection',
100            'getStudentDisconnectionTime',
101            'cancelReservation',
102            'saveAdminStaffEvaluation',
103            'getAnnouncement',
104            'getLessonRequest',
105            'translateText',
106            'lessonOnAirRequestLessonTime',
107            'uploadRecordedFile',
108            'uploadRecordedFileChivox',
109            'saveSelectedTextbook',
110            'saveConnectionSpeed',
111            'downloadChatLogFiles',
112            'uploadeCancelDeleteFile',
113            'checkForMaintenance',
114            'checkOngoingMaintenance',
115            'sendChivoxSlack',
116            'getTimezoneUserAndTeacher',
117            'uploadRecordedFileAsync',
118            'debugSocketLog',
119            'logTeacherDebugError',
120            'saveSkywayDebug',
121            'postEventCampaign',
122            'getTeachinMaterialEngName',
123            'fcmWebNotification',
124            'generatePeerID',
125            'uploadRecordedFileMergeAsync',
126            'studentLeftLessonUpdate',
127            'countUnsentMessage',
128            'saveTeacherLearnings',
129            'getRequestCount',
130            'googleCalendarCb',
131            'googleCalendarFlagUpdate',
132            'unlinkGoogleCalendar',
133            'saveBookmark',
134            'saveCallanStage',
135            'fetchBookmark',
136            'saveGameDataMemcache',
137            'gameDataMemcache',
138            'countUnreadSpotlightPost',
139            'sendSlackStreamCheckError'
140        );
141
142        $this->homeBase = $this->Auth->user('home_flg');
143    }
144
145    /**
146     * @api {get} /teacher/api/countUnreadSpotlightPost countUnreadSpotlightPost
147     * @apiName countUnreadSpotlightPost
148     * @apiGroup API
149     * @apiDescription Counts the number of unread spotlight posts for the teacher.
150     * @apiSampleRequest off
151     * 
152     * @apiSuccess {Number} count Returns the count of unread spotlight posts.
153     * 
154     * @apiErrorExample {json} Success-Response:
155     *     {
156     *       "count": 5
157     *     }
158     * 
159      * @apiErrorExample {js} Used in: AngularJS
160      * Location: "webroot/js/ng/app.js"
161     */
162    public function countUnreadSpotlightPost() {
163        $this->autoRender = false;
164        
165        $teacherId = $this->Auth->user('id');
166        
167        $db = $this->TeacherSpotlightView->getDataSource();
168        $subQuery = $db->buildStatement(
169            array(
170                'fields'         =>     array('TeacherSpotlightView.spotlight_id'),
171                'table'          =>     'teacher_spotlight_view',
172                'alias'          =>     'TeacherSpotlightView',
173                'conditions'     =>     array('TeacherSpotlightView.teacher_id' => $teacherId),
174            ),
175            $this->TeacherSpotlightView
176        );
177    
178        $count = $this->TeacherSpotlight->find('count', array(
179            'joins' => array(
180                array(
181                    'table' => 'teachers',
182                    'alias' => 'Teacher',
183                    'type' => 'INNER',
184                    'conditions' => array('TeacherSpotlight.teacher_id = Teacher.id')
185                )
186            ),
187            'conditions' => array(
188                'NOT' => array('TeacherSpotlight.teacher_id IN (' . $subQuery . ')'),
189                'TeacherSpotlight.status' => 1,
190                'Teacher.spotlight_flag' => 1
191            )
192        ));
193        return json_encode($count);
194    }
195    
196    /**
197     * @api {get} /teacher/api/checkSession checkSession()
198     * @apiName checkSession
199     * @apiGroup API
200     * @apiDescription Checks if the user's session is active.
201     * @apiSampleRequest off
202     * 
203     * @apiSuccess {Number} status Returns 1 if the session is active, 0 if not.
204     * 
205     * @apiErrorExample {json} Success-Response:
206     * {"status": 1}
207     * 
208      * @apiErrorExample {js} Used in: AngularJS
209      * Location: "webroot/js/recruitment/ng/services.js"
210     *
211      * @apiErrorExample {js} Used in: Ajax
212      * Location: "webroot/js/ajaxload.js"
213     */
214    public function checkSession() {
215        $this->autoRender = false;
216        if (!empty($this->Session->read('recruit'))) {
217            return 1;
218        }
219
220        if ($this->Auth->user('id')) {
221            $ts = TeacherStatusLogTable::getTS();
222            $onair = LessonOnairTable::getLessonOnairStatus();
223
224            // add loggers
225            $this->log("[TEACHER_FORCE_DISCONNECTION] teacher status -> " . json_encode($ts), "debug");
226            $this->log("[TEACHER_FORCE_DISCONNECTION] teacher status -> " . json_encode($onair), "debug");
227
228            if ($ts->status == 6 && !$onair) {
229                $this->Session->destroy();
230            }
231        }
232
233        $user_id = $this->Auth->user('id');
234        return empty($user_id) ? 0 : 1;
235    }
236
237    /**
238     * @api {post} /teacher/api/generatePeerID generatePeerID()
239     * @apiName generatePeerID
240     * @apiGroup API
241     * @apiDescription Generates a unique peer ID for the Twilio video chat.
242     * @apiSampleRequest off
243     * 
244     * @apiBody {String} [chatHash] Unique identifier for the chat session.
245     * @apiBody {Number} [userID] ID of the user (default: 0).
246     * @apiBody {String} [userType] Type of user (e.g., "teacher", "student") (default: "teacher").
247     * @apiBody {String} [testConnect] Optional parameter for testing (default: "").
248     * 
249     * @apiSuccess {String} peer_id The generated Twilio peer ID.
250     * @apiSuccess {String} peer_hash The hash corresponding to the generated peer ID.
251     * 
252     * @apiErrorExample {json} Success-Response:
253     *     {
254     *       "peer_id": "unique-peer-id-12345",
255     *       "peer_hash": "hashed-value-67890"
256     *     }
257     * 
258     * @apiErrorExample {js} Used in: Webrtc
259      * Location: "webroot/js/webrtcv2/connect.js"
260      * Location: "webroot/js/webrtcv2/connectEnvModal.js"
261      * Location: "webroot/js/webrtcv2/webrtc.testing.module.js"
262     */
263    public function generatePeerID($chatHash = "", $userID = 0, $userType = "teacher", $testConnect = ""){
264
265        $test = [$chatHash, $userID, $userType, $testConnect];
266        
267        // - load twilio
268        App::uses('myTwilio','Lib');
269
270        // - disallow rendering
271        $this->autoRender = false;
272
273        // - generate twilio peer ID and other meta information
274        $twilio = new myTwilio();
275
276        // - if request came from test_connect page
277        if ($testConnect == "test_connect") {
278            $twilio->ttl = 600;
279        }
280        
281        // - generate ice servers`
282        $twilioIceServers = $twilio->generateIceServers();
283
284        // - generate peer ID
285        $skyWayId = $twilio->generatePeerID($chatHash, $userType);
286
287        // - return hash
288        $skyWayHash = (array)json_decode($twilio->generateHash($skyWayId));
289
290        // - return hash and peer id
291        return json_encode(array('peer_id' => h($skyWayId), 'peer_hash' => h($skyWayHash)));
292    }
293
294    /**
295     * @api {post} /teacher/api/getEndTime getEndTime()
296     * @apiName getEndTime
297     * @apiGroup API
298     * @apiDescription Fetches the remaining time for the lesson to end.
299     * @apiSampleRequest off
300     * 
301     * @apiBody {String} chat_hash Unique identifier for the lesson chat.
302     * 
303     * @apiSuccess {String} min Minutes remaining until the lesson ends.
304     * @apiSuccess {String} sec Seconds remaining until the lesson ends.
305     * @apiSuccess {Number} end_time_stamp Timestamp of the lesson's end time (in seconds since Unix epoch).
306     * @apiSuccess {Number} [end_time_temp] Temporary end time timestamp, defaults to 25 minutes from now if chat_end_time is null.
307     * 
308     * @apiErrorExample {json} Success-Response:
309     *  Custom-End-Time-Response:
310     *     {
311     *       "min": "10",
312     *       "sec": "30",
313     *       "end_time_stamp": 1698129000
314     *     }
315     * 
316     *  Default-End-Time-Response:
317     *     {
318     *       "min": "25",
319     *       "sec": "00",
320     *       "end_time_stamp": null,
321     *       "end_time_temp": 1698130200
322     *     }
323     * 
324     * @apiErrorExample {js} Return type: GET
325     * Returns: die() if method is GET
326     */
327    public function getEndTime(){
328        $this->autoRender = false;
329        $this->layout = false;
330        if ($this->request->is('post')) {
331            $post = $this->request->data;
332            $chat_hash = (isset($post['chat_hash']))?$post['chat_hash']:'';
333            if (empty($chat_hash)) {
334                return '';
335            }
336
337            $onair = $this->LessonOnair->findByChatHash($chat_hash);
338            if (!$onair) {
339                return '';
340            }
341
342            if ($onair['LessonOnair']['chat_end_time'] != null ) {
343                $response = array(
344                    'min' => date('i', strtotime($onair['LessonOnair']['chat_end_time']) - time()),
345                    'sec'=> date('s', strtotime($onair['LessonOnair']['chat_end_time']) - time()),
346                    'end_time_stamp'=> strtotime($onair['LessonOnair']['chat_end_time'])
347                );
348            } else {
349                $response = array(
350                    'min' => '25',
351                    'sec'=> '00',
352                    'end_time_stamp'=> null,
353                    'end_time_temp'=> strtotime('+25 min')
354                );
355            }
356            return json_encode($response);
357        }else{
358            return '';
359        }
360    }
361
362    /**
363     * @api {post} /teacher/api/getNowTime getNowTime()
364     * @apiName getNowTime
365     * @apiGroup API
366     * @apiDescription Fetches the current server time and other lesson details.
367     * @apiSampleRequest off
368     * 
369     * @apiBody {String} chatHash Unique identifier for the lesson chat.
370     * @apiBody {Number} [studentFr=0] Optional flag to trigger face recognition.
371     * 
372     * @apiErrorExample {js} Used in: WebRTC
373      * Location: "webroot/js/recruitment/webrtcv2/event.common.js"
374      * Location: "webroot/js/webrtcv2/event.common.js"
375     */
376    public function getNowTime() {
377        $this->viewClass = 'Json';
378        $setView = array();
379        if ($this->request->is(array('post', 'ajax'))) {
380            $chatHash = $this->request->data['chatHash'];
381            $runFaceRecog = isset($this->request->data['studentFr']) ? $this->request->data['studentFr'] : 0;
382
383            // find onair
384            $this->LessonOnair->clear();
385            $onair = $this->LessonOnair->find('first', array(
386                'fields' => array(
387                    'LessonOnair.id',
388                    'LessonOnair.start_time',
389                    'LessonOnair.end_time',
390                    'LessonOnair.user_id',
391                    'LessonOnair.teacher_id',
392                    'LessonOnair.lesson_type',
393                    'LessonOnair.connect_count',
394                    'LessonOnair.connect_count_active_free',
395                    'LessonOnair.connect_count_active'
396                ),
397                'conditions' => array(
398                    'LessonOnair.chat_hash' => $chatHash
399                ),
400                'recursive' => -1
401            ));
402
403            if (empty($onair['LessonOnair']['end_time'])) {
404                $remainingTime = 0;
405            } else {
406                $endTime = $onair['LessonOnair']['end_time'];
407
408                // get remaining time
409                $nowTimeStamp = strtotime('now');
410                $endTimeStamp = strtotime($endTime);
411                $remainingTime = ($endTimeStamp - $nowTimeStamp);
412            }
413
414            // set view
415            $setView = [
416                'startTime' => ($onair && isset($onair['LessonOnair']['start_time']) && !empty($onair['LessonOnair']['start_time'])) ? $onair['LessonOnair']['start_time'] : null,
417                'nowTime' => date("Y-m-d H:i:s"),
418                'endTime' => ($onair && isset($onair['LessonOnair']['end_time']) && !empty($onair['LessonOnair']['end_time'])) ? $onair['LessonOnair']['end_time'] : null ,
419                'remainingTime' => $remainingTime,
420                'viewers' => isset($onair['LessonOnair']['connect_count']) ? $onair['LessonOnair']['connect_count'] : 0,
421                'active_viewers' => isset($onair['LessonOnair']['connect_count_active']) ? $onair['LessonOnair']['connect_count_active'] : 0                
422            ];
423
424            if ($onair && LessonOnairTable::isSubstituteTeacher($onair['LessonOnair']['teacher_id'], $onair['LessonOnair']['user_id'], $chatHash)) {
425                //include if not exist
426                if (!class_exists('myMemcached')) {
427                    App::uses('myMemcached', 'Lib');
428                }
429                // declare myMemcached
430                $memcached = new myMemcached();
431                $memcached->set(array(
432                    'key' => 'race-condition-lesson-'.$chatHash,
433                    'value' => 1,
434                    'expire' => 10//10 minutes
435                ));
436                $setView['endTime'] = myTools::myDate();
437                $setView['remainingTime'] = 0;
438                $setView['forceEnd'] = true;
439            }
440
441            # START FACE RECOGNITION FUNCTIONS!
442            if ($runFaceRecog && isset($onair['LessonOnair']['user_id'])) {
443                $userId = $onair['LessonOnair']['user_id'];
444                $settingName = Configure::read('setting_names.face_recognition');
445                $options = $this->SettingOption->getOptions($settingName);
446                $userTotalLesson = 0;
447                $setToRun = false;
448
449                #get monthly total lesson
450                $userTotalLesson = $this->LessonOnairsLog->useReplica()->find('count',array(
451                    'conditions' => array(
452                        'LessonOnairsLog.user_id' => $userId,
453                        'LessonOnairsLog.start_time >=' => date('Y-m-01 00:00:00'),
454                        'LessonOnairsLog.start_time <=' => date('Y-m-t 23:59:59'),
455                    )
456                ));
457
458                $this->LessonOnairsLog->openDBReplica();
459                $userTotalSudden = $this->LessonOnairsLog->find('count',array(
460                    'conditions' => array(
461                        'LessonOnairsLog.user_id' => $userId,
462                        'LessonOnairsLog.lesson_type' => 1,
463                        'LessonOnairsLog.start_time BETWEEN ? AND ? ' => array(date('Y-m-d 00:00:00', strtotime('-30 days')), date('Y-m-d 23:59:59', strtotime('-1 days')))
464                    )
465                ));
466                $this->LessonOnairsLog->closeDBReplica();
467                $this->log("[FACERECOG] user_id: {$userId}, userTotalSudden: {$userTotalSudden}, userTotalLesson: {$userTotalSudden}", 'lesson_bookmark');
468
469                $userTotalLesson++;
470                $currentLessonType = !empty($onair['LessonOnair']['lesson_type']) ? $onair['LessonOnair']['lesson_type'] : 1;
471
472                #check if face recognition set to run
473                if (
474                    $options &&
475                    ((isset($options['first_interval']) && trim($options['first_interval']) != "") ||
476                    (isset($options['second_interval']) && trim($options['second_interval']) != "") ||
477                    (isset($options['third_interval']) && trim($options['third_interval']) != ""))
478                ) {
479                    $setToRun = true;
480                }
481
482                if ($options && isset($options['target_lessons']) && $options['target_lessons'] <= $userTotalLesson && $setToRun) {
483                    $frInterval = $options;
484                    unset($frInterval['target_lessons']);
485                    unset($frInterval['frequency']);
486                    $frInterval = array_filter($frInterval);
487                    $frInterval = array_values($frInterval);
488                    $captureFlg = false;
489
490                    # check face auth
491                    $faceRegistered = $this->FaceRegistration->find('first', array(
492                        'fields' => array('chat_hash', 'created'),
493                        'conditions' => array(
494                            'FaceRegistration.acquisition_flg' => 1,
495                            'FaceRegistration.user_id' => $userId
496                        )
497                    ));
498
499                    $this->log("[FACERECOG] user_id: {$userId}, faceRegistered: " . json_encode($faceRegistered), 'lesson_bookmark');
500
501                    # control frequency capture
502                    if ($faceRegistered && isset($options['frequency'])) {
503                        $faceReg = $faceRegistered['FaceRegistration'];
504
505                        //-- disable facerecognation function 
506                        if (
507                            !empty($options['max_lesson_count_exemption']) 
508                            && $userTotalSudden <= $options['max_lesson_count_exemption']
509                        ) {
510                            $captureFlg = false;
511
512                        } elseif ($options['frequency'] == 0) {
513                            $captureFlg = true;
514                        } else {
515                            $countRecog = 1;
516                            $newRegister = false;
517                            $lastRecog = $this->FaceRecognition->find('first', array(
518                                'fields' => array('FaceRecognition.chat_hash', 'LessonOnairsLog.id'),
519                                'joins' => array(
520                                    array(
521                                        'type' => 'LEFT',
522                                        'table' => 'lesson_onairs_logs',
523                                        'alias' => 'LessonOnairsLog',
524                                        'conditions' => 'LessonOnairsLog.chat_hash = FaceRecognition.chat_hash'
525                                    ),
526                                ),
527                                'conditions' => array(
528                                    'FaceRecognition.created >=' => date('Y-m-01 00:00:00'),
529                                    'FaceRecognition.created <=' => date('Y-m-t 23:59:59'),
530                                    'FaceRecognition.created >=' => $faceReg['created'], # make sure after new registration
531                                    'FaceRecognition.user_id' => $userId
532                                ),
533                                'order' => 'FaceRecognition.id DESC'
534                            ));
535                            $this->log('[Check]' . json_encode($lastRecog) , 'debug');
536                            $this->log("[FACERECOG] user_id: {$userId}, lastRecog: " . json_encode($lastRecog), 'lesson_bookmark');
537
538                            if ($lastRecog) {
539                                $lastRecog = $lastRecog['LessonOnairsLog'];
540                                #get ID of last lesson
541                                $countPrev = $this->LessonOnairsLog->find('count', array(
542                                    'conditions' => array(
543                                        'LessonOnairsLog.user_id' => $userId,
544                                        'LessonOnairsLog.id >=' => $lastRecog['id'],
545                                    )
546                                ));
547
548                                #count included in DB to form lesson sequence
549                                $countRecog = $countPrev > 0 ?     ($countPrev + 1) : $countRecog;
550                            } else {
551
552                                if (date('Y-m') == date('Y-m', strtotime($faceReg['created']))) {
553
554                                    $lastRegis = $this->FaceRegistration->find('first', array(
555                                        'fields' => array('LessonOnairsLog.id'),
556                                        'joins' => array(
557                                            array(
558                                                'type' => 'LEFT',
559                                                'table' => 'lesson_onairs_logs',
560                                                'alias' => 'LessonOnairsLog',
561                                                'conditions' => 'LessonOnairsLog.chat_hash = FaceRegistration.chat_hash'
562                                            ),
563                                        ),
564                                        'conditions' => array(
565                                            'FaceRegistration.user_id' => $userId,
566                                            'FaceRegistration.chat_hash' => $faceReg['chat_hash'],
567                                        ),
568                                        'order' => 'FaceRegistration.id DESC'
569                                    ));
570
571                                    $lastRegis = $lastRegis['LessonOnairsLog'];
572                                    $countPrev = $this->LessonOnairsLog->find('count', array(
573                                        'conditions' => array(
574                                            'LessonOnairsLog.user_id' => $userId,
575                                            'LessonOnairsLog.id >=' => $lastRegis['id'],
576                                        )
577                                    ));
578
579                                    #count included in DB to form lesson sequence
580                                    $countRecog = $countPrev > 0 ?     ($countPrev + 1) : $countRecog;
581                                    $newRegister = true;
582                                }
583
584                            }
585
586                            $this->log('[FR] > chat_hash: ' . $chatHash . ', new reg : ' . ($newRegister ? 'true' : 'false') . ', countRecog: ' . $countRecog, 'debug');
587                            $this->log("[FACERECOG] user_id: {$userId}, newRegister: {$newRegister}, countRecog: {$countRecog} ", 'lesson_bookmark');
588                            $seq = (int)$options['frequency'];
589
590                            if ($this->isSequenceOf($countRecog, $seq, $newRegister)) {
591                                $captureFlg = true;
592                            }
593                        }
594
595                        #check match
596                        $sameChatHash = $this->FaceRecognition->find('count', array(
597                            'conditions' => array(
598                                'FaceRecognition.created >=' => date('Y-m-01 00:00:00'),
599                                'FaceRecognition.created <=' => date('Y-m-t 23:59:59'),
600                                'FaceRecognition.user_id' => $userId,
601                                'FaceRecognition.mismatch_flg' => 0,
602                                'FaceRecognition.chat_hash' => $chatHash,
603                            )
604                        ));
605
606                        #if current chathash registration || current mismatch_flg already 0
607                        if ($faceReg['chat_hash'] == $chatHash || $sameChatHash) {
608                            $captureFlg = false;
609                        }
610                    } else {
611                        $captureFlg = true;
612                    }
613
614                    $setView['userTotalLesson'] = $userTotalLesson;
615                    $setView['captureTime'] = $frInterval;
616                    $setView['captureFlg'] = $captureFlg;
617                } else {
618                    $setView['isRunning'] = true;
619                }
620            }
621            # END FACE RECOGNITION FUNCTIONS!
622        }
623
624        $this->set('result', $setView);
625        $this->set('_serialize', 'result');
626    }
627
628    private function isSequenceOf($num, $sequence, $isNewRegistered) {
629        if ($num == 1 && !$isNewRegistered) {
630            return true;
631        } elseif ($num < $sequence) {
632            return false;
633        } else {
634            $ctr = 1;
635            $sequence = $sequence + 1;
636            while($ctr <= $num) {
637                $ctr = $ctr + $sequence;
638                if ($num == $ctr) {
639                    return true;
640                }
641            }
642        }
643        return false;
644    }
645
646    /**
647     * @api {get} /teacher/api/checkForMaintenance checkForMaintenance()
648     * @apiName checkForMaintenance
649     * @apiGroup API
650     * @apiDescription Checks if the application is currently in maintenance mode.
651     * @apiSampleRequest off
652     * 
653     * @apiSuccess {Number} is_maintenance Indicates if the application is currently in maintenance mode (1 for true, 0 for false).
654     * @apiSuccess {String} user_maintenance_time The formatted time of the maintenance in the user's timezone.
655     * @apiSuccess {String} [chat_message] A message indicating the maintenance timing for the user, if applicable.
656     * 
657     * @apiErrorExample {json} Success-Response:
658     *     {
659     *       "is_maintenance": 1,
660     *       "user_maintenance_time": "2:00 AM",
661     *       "chat_message": "--It is 5 minutes before the starting of routine maintenance. We will finish the lesson at 2:00 AM (UTC+5) Asia/Tokyo."
662     *     }
663     * 
664     * @apiErrorExample {js} Used in: WebRTC
665      * Location: "webroot/js/webrtcv2/event.common.js"
666     */
667    public function checkForMaintenance() {
668        $this->autoRender = false;
669        $response = array(
670            'is_maintenance' => 0,
671            'user_maintenance_time' => '2:00 AM'
672        );
673        if (!myTools::checkCompanyIP($_SERVER['REMOTE_ADDR'])) {
674            $conditions = array(
675                'is_active' => 1,
676                'start_date <= DATE_ADD(NOW(), INTERVAL 5 MINUTE)',
677                'end_date >= NOW()'
678            );
679            $maintenanceModel = ClassRegistry::init('Maintenance');
680            $maintenanceModel->openDBReplica();
681            $maintenance = $maintenanceModel->find('first',array(
682                'conditions' => $conditions
683            ));
684            $maintenanceModel->closeDBReplica();
685            if ($maintenance) {
686                $response['is_maintenance'] = 1;
687                $teacher_timezone_id = $this->Teacher->getUpdatedTimezoneID($this->Auth->user('id'));
688                if ($teacher_timezone_id) {
689                    $timezones = $this->Timezone->getFormattedRecruitTimezones();
690                    $userTimeZoneData = $timezones[$teacher_timezone_id];
691                    $datetime = date($maintenance['Maintenance']['start_date']); // maintenance time
692                    $timestamp = strtotime($datetime);
693                    $fromTime = $timestamp + (($userTimeZoneData['jp_time_diff'] / 60) * 60 * 60);
694                    $userMaintenanceTime = date("g:i A", $fromTime);
695                    $uData = $this->Timezone->getTimezoneUserData($teacher_timezone_id);
696                    $response['user_maintenance_time'] = $userMaintenanceTime;
697                    $chatMessage = __d('waiting','--It is 5 minutes before the starting of routine maintenance. We will finish the lesson at ') . $userMaintenanceTime. ' (UTC'.$uData['Timezone']['utc_offset'].') ' .$userTimeZoneData['data-timezone_name'] .'.';
698                    $response['chat_message'] = $chatMessage;
699                }
700            }
701        }
702        echo json_encode($response);
703    }
704
705    /**
706     * @api {get} /teacher/api/checkOngoingMaintenance checkOngoingMaintenance()
707     * @apiName checkOngoingMaintenance
708     * @apiGroup API
709     * @apiDescription Checks if the application is currently in maintenance mode.
710     * @apiSampleRequest off
711     * 
712     * @apiSuccess {Boolean} is_maintenance Indicates if the application is currently in maintenance mode.
713     * @apiSuccess {String} current_url The current URL of the request.
714     * 
715     * @apiErrorExample {json} Success-Response:
716     *     {
717     *       "is_maintenance": false,
718     *       "current_url": "https://example.com/current-page"
719     *     }
720     * 
721      * @apiErrorExample {js} Used in: AngularJS
722      * Location: "webroot/js/ng/app.js"
723
724     * @apiErrorExample {js} Used in: WebRTC
725      * Location: "webroot/js/webrtcv2/event.common.js"
726     */
727    public function checkOngoingMaintenance() {
728        $this->autoRender = false;
729        echo json_encode(
730            array(
731                'is_maintenance' => MaintenanceTable::isMaintenance(),
732                'current_url' => myTools::getUrl()
733            ));
734    }
735
736    /**
737     * @api {post} /teacher/api/getLessonStatus getLessonStatus()
738     * @apiName getLessonStatus
739     * @apiGroup API
740     * @apiDescription Fetches the current status of the lesson.
741     * @apiSampleRequest off
742     * 
743     * @apiBody {String} chat_hash Unique identifier for the chat session.
744     * @apiBody {Object} [teacher_audio_data] Audio data from the teacher.
745     * @apiBody {Object} [student_audio_data] Audio data from the student.
746     *
747      * @apiErrorExample {js} Used in: AngularJS
748      * Location: "webroot/js/ng/app.js"
749     */
750    public function getLessonStatus(){
751        $this->viewClass = 'Json';
752        $response = array();
753
754        // check if has post
755        if ($this->request->is('post')) {
756            $post = $this->request->data;
757            $chat_hash = (isset($post['chat_hash']))?$post['chat_hash']:'';
758
759            // empty chat hash
760            if (!empty($chat_hash)) {
761                $this->LessonOnair->recursive = -1;
762                $onair = $this->LessonOnair->findByChatHash($chat_hash);
763
764                // - check if has onair
765                if ($onair) {
766                    // - if has status
767                    if ($onair['LessonOnair']['status']) {
768                        //check modified date
769                        $connected = ((strtotime($onair['LessonOnair']['modified']) - time()) <= 40) ? 1 : 0;
770                        // set default response
771                        $response = array(
772                            'status' => $onair['LessonOnair']['status'],
773                            'connect_flg' => $onair['LessonOnair']['connect_flg'],
774                            'counselor_flag' => $onair['LessonOnair']['counselor_flag'],
775                            'current_time' => array(
776                                'date_time' => date('Y-m-d H:i:s'),
777                                'y' => date('Y'),
778                                'm' => date('m'),
779                                'd' => date('d'),
780                                'h' => date('H'),
781                                'i' => date('i'),
782                                's' => date('s'),
783                                'time_stamp' => strtotime('now')
784                            ),
785                            'connected' => $connected,
786                            'lesson_current_time' => time(),
787                            'lesson_end_time' => isset($onair['LessonOnair']['end_time']) ? strtotime($onair['LessonOnair']['end_time']) : null,
788                            'is_live' =>  isset($onair['LessonOnair']['live_lesson_flg']) ? $onair['LessonOnair']['live_lesson_flg'] : 0,
789                            'viewers' => isset($onair['LessonOnair']['connect_count']) ? $onair['LessonOnair']['connect_count'] : 0,
790                            'active_viewers' => isset($onair['LessonOnair']['connect_count_active']) ? $onair['LessonOnair']['connect_count_active'] : 0
791                        );
792
793                        $memKey = 'mem_last_4min_or_less_reserve_cancel_' . $onair['LessonOnair']['teacher_id'];
794                        $memcached = new myMemcached();
795                        $memLast4MinOrLessResCancelFlg = $memcached->get($memKey);
796                        $min = date('i');
797                        if (
798                            (($min >= 26 && $min <= 30) || ($min >= 56 && $min <= 59) || $min == 0) &&
799                            ($this->Auth->user('home_flg') && $this->Auth->user('counseling_flg') == 0 ) && !$memLast4MinOrLessResCancelFlg
800                        ) {
801
802                            if ($min >= 26 && $min <= 30) {
803                                $lessonTime = date('Y-m-d H:30:00');
804                            } elseif ($min == 0) {
805                                $lessonTime = date('Y-m-d H:00:00');
806                            } else {
807                                $lessonTime = date('Y-m-d H:00:00', strtotime('+1 hour'));
808                            }
809
810                            $lessonTimeInSec = strtotime($lessonTime);
811                            $cancelledDate = date('Y-m-d H:i:s', strtotime('-5 minutes', $lessonTimeInSec));
812
813                            // NC-4820: check if reservation was cancelled 4 minutes or less before the lesson time
814                            $reservationCancelled = $this->LessonScheduleCancel->find('first', array(
815                                'fields' => array('cancelled_date'),
816                                'conditions' => array(
817                                    'teacher_id' => $onair['LessonOnair']['teacher_id'],
818                                    'lesson_time' => $lessonTime,
819                                    'cancelled_date >=' => $cancelledDate,
820                                    'cancelled_date <' => $lessonTime,
821                                    'status' => 20 // student cancelled less than 1 hour
822                                ),
823                                'recursive' => -1
824                            ));
825
826                            if ($reservationCancelled) {
827                                $response['redirectToNotStandby'] = 1;
828                                $response['chatHash'] = $chat_hash;
829
830                                // set memcached last 4 minute or less reservation cancel flg
831                                if (!$memLast4MinOrLessResCancelFlg) {
832                                    $memcached->set(array(
833                                        'key' => $memKey,
834                                        'value' => true,
835                                        'expire' => 300 // 5 minutes
836                                    ));
837                                }
838                            }
839                        }
840
841                        //NC-7795 set new interval checker to 15 seconds if 5 mins before lesson time
842                        if ((($min >= 26 && $min <= 30) || ($min >= 31 && $min <= 36) || ($min >= 56 && $min <= 59) || ($min >= 00 && $min <= 06)) && $onair['LessonOnair']['status'] == 2) {
843                            $response["setNewIntervalChecker"] = 15000; //15 seconds
844                        } else {
845                             $response["setNewIntervalChecker"] = 30000; //30 seconds
846                        }
847
848                        // if lesson status is reserved, add to teacher status logs
849                        if ($onair['LessonOnair']['status'] == 2) {
850                            $this->addTeacherStatusLog($onair['LessonOnair'],3,$chat_hash);
851                        }
852
853                        // if onair status is lesson, inform teacher
854                        if ($onair['LessonOnair']['status'] == 3) {
855                            $response['lesson_type'] = $onair['LessonOnair']['lesson_type'];
856                            $response['lesson_student_id'] = $onair['LessonOnair']['user_id'];
857
858                            // - if live lesson
859                            if (
860                                !empty($onair['LessonOnair']['lesson_schedule_id']) &&
861                                $onair['LessonOnair']['live_lesson_flg'] == 1
862                            ) {
863                                // - get current live teacher
864                                $this->Teacher->openDBReplica();
865                                $arrTeacherLiveLesson = $this->Teacher->find('first', array(
866                                    'fields' => array(
867                                        'id',
868                                        'live_lesson_sort',
869                                        'live_lesson_flg'
870                                    ),
871                                    'conditions' => array(
872                                        'id' => $onair['LessonOnair']['teacher_id']
873                                    ),
874                                    'recursive' => -1
875                                ));
876                                $this->Teacher->closeDBReplica();
877
878                                // - if has live lesson sort data
879                                if (
880                                    isset($arrTeacherLiveLesson['Teacher']['id']) && 
881                                    isset($arrTeacherLiveLesson['Teacher']['live_lesson_sort']) && 
882                                    $arrTeacherLiveLesson['Teacher']['live_lesson_sort'] != 1
883                                ) {
884                                    $updateSql =  "
885                                        UPDATE 
886                                            `teachers`
887                                        SET 
888                                            `teachers`.`live_lesson_sort` = 1
889                                        WHERE
890                                            id = " . $arrTeacherLiveLesson['Teacher']['id'] . "
891                                    ";
892                                    $this->Teacher->query($updateSql);
893                                }
894                            }
895
896                            // get current user
897                            $this->User->clear();
898                            $this->User->openDBReplica();
899                            $userData = $this->User->findById($onair['LessonOnair']['user_id']);
900                            $this->User->closeDBReplica();
901                            
902                            if ($userData) {
903                                $userTable = new UserTable($userData['User']);
904                                //  NJ-279: check if user's corporate id is not included on the list of disabled file uploading
905                                $response['enable_teacher_file_upload'] = ($userTable->corporate_id != "NULL" && !in_array((int)$userTable->corporate_id, Configure::read("corporate_ids_disabled_file_upload")));
906                            }
907                        }
908
909                        // check if lesson is reservation
910                        if (!empty($onair['LessonOnair']['lesson_schedule_id'])) {
911                            $lessonScheduleId = $onair['LessonOnair']['lesson_schedule_id'];
912                            $this->LessonSchedule->openDBReplica();
913                            $reservation = $this->LessonSchedule->find('first', array(
914                                'fields' => array('LessonSchedule.live_lesson_flg'),
915                                'conditions' => array('LessonSchedule.id' => $lessonScheduleId),
916                                'recursive' => -1
917                            ));
918                            $this->LessonSchedule->closeDBReplica();
919
920                            // update live_lesson_flg
921                            if (!empty($reservation['LessonSchedule']) && $reservation['LessonSchedule']['live_lesson_flg'] == 1 && $onair['LessonOnair']['live_lesson_flg'] != 1) {
922                                $updateSql =  "
923                                    UPDATE 
924                                        `lesson_onairs`
925                                    SET 
926                                        `lesson_onairs`.`live_lesson_flg` = 1
927                                    WHERE
928                                        lesson_schedule_id = " . $lessonScheduleId . "
929                                ";
930                                $this->LessonOnair->query($updateSql);
931                            }
932                        }
933
934                        // Check if teacher audio data is supplied
935                        if (isset($post['teacher_audio_data']) && isset($post['student_audio_data'])) {
936                            $this->saveAudioMemcached($memcached, $post);
937                        }
938                    }
939                    
940                    // - if has connect flag
941                    if ($onair['LessonOnair']['connect_flg'] == 0 && $onair['LessonOnair']['status'] == 2) {
942                        $memKeyOnair = "lesson_onair_connect_flg_" . $chat_hash;
943                        $memcached = new myMemcached();
944                        $checkMemcachedOnair = $memcached->get($memKeyOnair);
945                        
946                        // - check if key was already set
947                        if (!$checkMemcachedOnair) {
948                            // - set key to true
949                            $memcached->set(array(
950                                'key' => $memKeyOnair,
951                                'value' => true,
952                                'expire' => 1500 // 5 minutes
953                            ));
954                            
955                            // - set slack message
956                            $mySlack = new mySlack();
957                            $mySlack->token = "xoxp-392902820692-393103987059-747628176676-c8ea0bf485312302ce3788cc55100b30";
958                            $mySlack->username = "Bad Teacher Connection Debug";
959                            $mySlack->channel = myTools::checkChannel("#bad-teacher-connections", "#bad-teacher-connections-dev");
960                            $mySlack->text = "";
961                            $mySlack->text .= "```";
962                            $mySlack->text .= "chat_hash: " . $chat_hash;
963                            $mySlack->text .= "\nteacher_id: " . $onair['LessonOnair']['teacher_id'];
964                            $mySlack->text .= "\nwait_start_time: " . $onair['LessonOnair']['wait_start_time'];
965                            $mySlack->text .= "```";
966                            
967                            // - send slack message
968                            $test = $mySlack->sendSlack();
969                        }
970                    }
971                    
972                    //- load class
973                    if (!class_exists('myRedis')) {
974                        App::uses('myRedis', 'Lib');
975                    }
976                    $redis = new myRedis();
977
978                    //- refresh lesson onair modified
979                    $redis->set([
980                        'key' => 'lesson_onair_modified_' . $chat_hash,
981                        'value' => time(),
982                        'expire' => 3600 //- expire in 1 hour
983                    ]);
984
985                // check last lesson onairs log
986                } else {
987                    // init memcached
988                    $memcached = new myMemcached();
989
990                    // set mem key
991                    $memKey = $chat_hash.'_student_cancel_in_time';
992
993                    // get student cancel in time chat hash memcached, set on lessonair->studentCancelInTime
994                    $studentCancelInTimeMem = $memcached->get($memKey);
995
996                    // if memcache exist for chat hash
997                    if($studentCancelInTimeMem){
998
999                        // set cookie for home
1000                        if( $this->Auth->user('home_flg') && $this->Auth->user('counseling_flg') == 0 ){
1001                            // set var for not standby
1002                            $teacherStatusCookie = array(
1003                                'status_id' => 5,
1004                                'break_id' => 0,
1005                                'break_other' => ''
1006                            );
1007
1008                            // set cookie for not standby
1009                            $this->Cookie->write('TEACHER_STATUS_' . $this->Auth->user('id'), $teacherStatusCookie, false, 1800);
1010                        }
1011
1012                        // set response
1013                        $response['student_cancel_in_time'] = true;
1014
1015                        // delete memcached
1016                        $memcached->delete($memKey);
1017                    }else{
1018
1019                        // get lesson onairs logs
1020                        $lessonLog = $this->LessonOnairsLog->find('first', array(
1021                            'fields' => ['LessonOnairsLog.*', 'LessonOnairsLogsExtend.leave_lesson'],
1022                            'joins' => array(
1023                                array(
1024                                    'table' => 'lesson_onairs_logs_extend',
1025                                    'alias' => 'LessonOnairsLogsExtend',
1026                                    'type' => 'LEFT',
1027                                    'conditions' => array('LessonOnairsLogsExtend.log_id = LessonOnairsLog.id')
1028                                )
1029                            ),
1030                            'conditions' => array(
1031                                'LessonOnairsLog.chat_hash' => $chat_hash
1032                            )
1033                        ));
1034
1035                        //popup browser refresh
1036                        if (!$lessonLog) {
1037                            $this->Cookie->write('dialog_notice_browser_refresh', 1);
1038                        }
1039
1040                    }
1041
1042                    // check if lesson finish exists
1043                    if (isset($lessonLog['LessonOnairsLog']['lesson_finish'])) {
1044                        // if forcefully terminated by lesson
1045                        if (
1046                            $lessonLog['LessonOnairsLog']['lesson_finish'] == 6 || 
1047                            ($lessonLog['LessonOnairsLog']['lesson_finish'] == 1 && 
1048                            $lessonLog['LessonOnairsLogsExtend']['leave_lesson'])
1049                        ) {
1050
1051                            if (isset($lessonLog['LessonOnairsLog']['start_time']) && isset($lessonLog['LessonOnairsLog']['end_time'])) {
1052                                // get  endtime
1053                                $startTime = $lessonLog['LessonOnairsLog']['start_time'];
1054                                $endTime = $lessonLog['LessonOnairsLog']['end_time'];
1055                                $currentTime = time();
1056                                if (strtotime($endTime) > $currentTime) {
1057                                    $endTime = date('Y-m-d H:i:s', $currentTime);
1058                                }
1059
1060                                // check if lesson time is greater than 5 minutes -> prepare Other
1061                                if ((strtotime($endTime) - strtotime($startTime)) > 300) {
1062                                    $this->log("[LESSON_FINISH_STUDENT_FORCE_TERMINATE] setting teacher to other for 3 minutes break -> " . json_encode($lessonLog), 'debug');
1063
1064                                    // set cookie for prepare other
1065                                    $teacherStatusCookie = array(
1066                                        'status_id' => 4,
1067                                        'break_id' => 4,
1068                                        'break_other' => 'after_lesson_other'
1069                                    );
1070                                    $this->log(__METHOD__ . " Setting prepare other cookies! : " . json_encode($teacherStatusCookie), "debug");
1071                                    $this->Cookie->write('TEACHER_STATUS_' . $this->Auth->user('id'), $teacherStatusCookie, false, 1800);
1072
1073
1074                                } else {
1075                                    $this->log("[LESSON_FINISH_STUDENT_FORCE_TERMINATE] lessontime is less than 5 minutes cannot prepare other -> " . json_encode($lessonLog), 'debug');
1076                                }
1077                            } else {
1078                                $this->log("[LESSON_FINISH_STUDENT_FORCE_TERMINATE] teacher cannot prepare other -> " . json_encode($lessonLog), 'debug');
1079                            }
1080                        }
1081
1082                        // NJ-32876
1083                        $memcached = new myMemcached();
1084                        $cachedKey = "midway_lesson_completed_cached_" . $chat_hash;
1085                        $midway_lesson_completed_cached = $memcached->get($cachedKey);
1086
1087                        if (!empty($midway_lesson_completed_cached)) {
1088                            $memcached->delete($cachedKey);
1089                            $response['has_midway_lesson_completed'] = 1;
1090                        }
1091                        // NJ-32876 - end
1092
1093                        // set lesson finish
1094                        $response['lesson_finish'] = $lessonLog['LessonOnairsLog']['lesson_finish'];
1095
1096                        // set lesson current time
1097                        $response['lesson_current_time'] = time();
1098
1099                        // set lesson end time
1100                        $response['lesson_end_time'] = isset($lessonLog['LessonOnairsLog']['end_time']) ? strtotime($lessonLog['LessonOnairsLog']['end_time']) : null;
1101                    }
1102                }
1103            }
1104        }
1105
1106        // set return
1107        $this->set('result', $response);
1108        $this->set('_serialize', 'result');
1109    }
1110
1111    /**
1112    *  // Check if teacher has a recent teacher audio data memcached if none ->add if has ->update
1113    * // get memcache data by key
1114    * @return mixed
1115    */
1116    private function saveAudioMemcached($memcached, $params) {
1117        $chatHash = $params['chat_hash'];
1118        $teacherAudioData = $params['teacher_audio_data'];
1119        $teacherAudioInterval = $params['teacher_audio_interval'];
1120        $studentAudioData = $params['student_audio_data'];
1121        $studentAudioInterval = $params['student_audio_interval'];
1122
1123        $teacherMemkey = 'teacher_audio_data_acquisition_' . $chatHash;
1124        $teacherMemkey2 = 'teacher_audio_interval_' . $chatHash;
1125        $memTeacherAudioData = $memcached->get($teacherMemkey);
1126
1127        //creating or updating memcache (teacher)
1128        if ($chatHash && $teacherAudioData) {
1129            if ($memTeacherAudioData) {
1130                $memcached->set(array(
1131                    'key' => $teacherMemkey,
1132                    'value' => $teacherAudioData,
1133                ));
1134                $memcached->set(array(
1135                    'key' => $teacherMemkey2,
1136                    'value' => $teacherAudioInterval,
1137                ));
1138            } else {
1139                $memcached->set(array(
1140                    'key' => $teacherMemkey,
1141                    'value' => $teacherAudioData,
1142                    'expire' => 1800 // 30 mins
1143                ));
1144                $memcached->set(array(
1145                    'key' => $teacherMemkey2,
1146                    'value' => $teacherAudioInterval,
1147                    'expire' => 1800 // 30 mins
1148                ));
1149            }
1150        }
1151
1152        $studentMemKey = 'student_audio_data_acquisition_' . $chatHash;
1153        $studentMemKey2 = 'student_audio_interval_' . $chatHash;
1154        $memStudentAudioData = $memcached->get($studentMemKey);
1155
1156        //creating or updating memcache (student)
1157        if ($chatHash && $studentAudioData) {
1158            if ($memStudentAudioData) {
1159                $memcached->set(array(
1160                    'key' => $studentMemKey,
1161                    'value' => $studentAudioData,
1162                ));
1163                $memcached->set(array(
1164                    'key' => $studentMemKey2,
1165                    'value' => $studentAudioInterval,
1166                ));
1167            } else {
1168                $memcached->set(array(
1169                    'key' => $studentMemKey,
1170                    'value' => $studentAudioData,
1171                    'expire' => 1800 // 30 mins
1172                ));
1173                $memcached->set(array(
1174                    'key' => $studentMemKey2,
1175                    'value' => $studentAudioInterval,
1176                    'expire' => 1800 // 30 mins
1177                ));
1178            }
1179        }
1180    }
1181
1182    private function addTeacherStatusLog($lessonOnairs,$status,$chatHash) {
1183        $ttl = TeacherStatusLogTable::getTeacherStatusByTeacher($lessonOnairs['teacher_id']);
1184        if ($ttl->status != $status && $ttl->status != 7) {
1185            $data = array(
1186                'teacher_id' => $lessonOnairs['teacher_id'],
1187                'workstation_id' => $ttl->getWorkstationId(),
1188                'status' => $status,
1189                'chat_hash' => $chatHash
1190            );
1191            TeacherStatusLogTable::save($data);
1192            TeacherStatusTable::save($data);
1193        }
1194    }
1195
1196    /**
1197     * @api {post} /teacher/api/getProfile getProfile()
1198     * @apiName getProfile
1199     * @apiGroup API
1200     * @apiDescription Retrieves the profile of a user based on the user ID.
1201     * @apiSampleRequest off
1202     * 
1203     * @apiBody {String} s_user_id The ID of the user whose profile is to be retrieved.
1204     */
1205    
1206    public function getProfile(){
1207        $this->layout = false;
1208        if ($this->request->is('post')) {
1209            $post = $this->request->data;
1210            $userId = (isset($post['s_user_id']))?$post['s_user_id']:'';
1211            if (empty($userId)) {
1212                die('');
1213            }
1214            $this->User->openDBReplica();
1215            $userData = $this->User->findById($userId);
1216            $this->User->closeDBReplica();
1217            if (!$userData) {
1218                die('');
1219            }
1220
1221            $userTable = new UserTable($userData['User']);
1222            $this->set('user',$userTable);
1223
1224            $this->set('enquate1Options', UserTable::getUserEnquate1(json_decode($userTable->enquate1)));
1225            $this->set('enquate2Options', UserTable::getUserEnquate2(json_decode($userTable->enquate2)));
1226            $this->set('enquate3Options', UserTable::getUserEnquate3(json_decode($userTable->enquate3)));
1227            $this->set('enquate4Options', UserTable::getUserEnquate4(json_decode($userTable->enquate4)));
1228
1229        } else {
1230            die();
1231        }
1232    }
1233
1234    /**
1235     * @api {post} /teacher/api/getHeader getHeader()
1236     * @apiName getHeader
1237     * @apiGroup API
1238     * @apiDescription Retrieves lesson header information based on the chat hash, including user details, lesson count, and feedback scores.
1239     * 
1240     * @apiBody {String} chat_hash The chat hash associated with the lesson.
1241     * 
1242     * @apiSampleRequest off
1243     * 
1244     * @apiErrorExample {js} Return Type: View
1245     * Location: "view/Api/get_header.php"
1246     */
1247    public function getHeader() {
1248        $this->layout = false;
1249        if ($this->request->is('post')) {
1250            $post = $this->request->data;
1251            $chat_hash = (isset($post['chat_hash']))?$post['chat_hash']:'';
1252            if (!$chat_hash) {
1253                exit;
1254            }
1255
1256            // onairデータ取得
1257            $onair = $this->LessonUserOnair->findByChatHash($chat_hash);
1258            if (!$onair) {
1259                exit;
1260            }
1261
1262            $user_id = $onair['LessonUserOnair']['user_id'];
1263            $teacher_id = $onair['LessonUserOnair']['teacher_id'];
1264
1265            // 会員情報取得
1266            $user = $this->User->findById($user_id);
1267            if (!$user) {
1268                exit;
1269            }
1270
1271            // 同一講師・生徒でのレッスン回数取得
1272            $this->UsersLessonNote->openDBReplica();
1273            $lesson_cnt = $this->UsersLessonNote->find('count', array('conditions' => array(
1274                'UsersLessonNote.user_id'    => $user_id,
1275                'UsersLessonNote.teacher_id' => $teacher_id,
1276            )));
1277            $this->UsersLessonNote->closeDBReplica();
1278
1279            $enquate6_str='';
1280            if (empty($user['User']['enquate6'])) {
1281                $user['User']['enquate6']=2;
1282            }
1283            $enquate6 = UserTable::getUserEnquate6Eng($user['User']['enquate6']);
1284            $enquate6_str=$enquate6[$user['User']['enquate6']];
1285
1286            $enquate7_str='';
1287            if (empty($user['User']['enquate7'])) {
1288                $user['User']['enquate7']=2;
1289            }
1290            $enquate7 = UserTable::getUserEnquate7Eng($user['User']['enquate7']);
1291            $enquate7_str=$enquate7[$user['User']['enquate7']];
1292
1293            //*NC-4966
1294            $workstation = $this->checkWorkstationSession();
1295
1296            # save to teacher_status log table
1297            $teacherStatusData = array(
1298                'teacher_id' => $this->Auth->user('id'),
1299                'workstation_id' => $workstation['id'],
1300                'status' => 1
1301            );
1302            TeacherStatusLogTable::save($teacherStatusData);
1303            TeacherStatusTable::save($teacherStatusData);
1304
1305            myTools::init_session(true);
1306            $this->Session->write('Teacher.status', '1');
1307            myTools::init_session();
1308
1309            $this->set('teacher', $onair['Teacher']);
1310            $this->set('user',    $user['User']);
1311            $this->set('onair',   $onair['LessonUserOnair']);
1312            $this->set('lesson_cnt', $lesson_cnt);
1313            $this->set('enquate6_str', $enquate6_str);
1314            $this->set('enquate7_str', $enquate7_str);
1315
1316        } else {
1317            exit;
1318        }
1319    }
1320
1321    /**
1322     * @api {post} /teacher/api/getMemo getMemo()
1323     * @apiName getMemo
1324     * @apiGroup API
1325     * @apiDescription Retrieves the teacher's memo for a specific lesson based on the student, teacher, and lesson information.
1326     * 
1327     * @apiBody {Number} t_user_id The teacher's user ID.
1328     * @apiBody {Number} s_user_id The student's user ID.
1329     * @apiBody {String} lesson_id The lesson ID in the format `classId__chapterId`.
1330     * 
1331     * @apiSampleRequest off
1332     */
1333    
1334    public function getMemo() {
1335        $this->autoRender = false;
1336        if ($this->request->is('post')) {
1337            $post = $this->request->data;
1338            $teacherId = (isset($post['t_user_id']))?$post['t_user_id']:'';
1339            $userId = (isset($post['s_user_id']))?$post['s_user_id']:'';
1340            $lessonId = (isset($post['lesson_id']))?$post['lesson_id']:'';
1341            //validating the required field
1342            if (empty($teacherId) || empty($userId) || empty($lessonId)) {
1343                $this->outputMemo(0,'',$lessonId);
1344            }
1345
1346            //check if teacher is exists
1347            $teacherData = $this->Teacher->findById($teacherId);
1348            if (!$teacherData) {
1349                $this->outputMemo(0,'',$lessonId);
1350            }
1351
1352            //check if user is exists
1353            $userData = $this->User->findById($userId);
1354            if (!$userData) {
1355                $this->outputMemo(0,'',$lessonId);
1356            }
1357
1358            list($classId,$chapterId) = explode('__',$lessonId);
1359            $lessonData = $this->LessonText->findByClassIdAndChapterId($classId,$chapterId);
1360            if (!$lessonData) {
1361                $this->outputMemo(0,'',$lessonId);
1362            }
1363
1364            $userclassData = $this->UsersClass->findByUserIdAndClassIdAndChapterIdAndTeacherId($userId,$classId,$chapterId,$teacherId);
1365            if (!$userclassData) {
1366                $this->outputMemo(0,'',$lessonId);
1367            }
1368            $this->outputMemo(1,$userclassData['UsersClass']['teacher_memo'],$lessonId);
1369        } else {
1370            $this->outputMemo();
1371        }
1372    }
1373
1374
1375    private function outputMemo($result=0,$memo='',$lessionId='') {
1376        $data = array(
1377            'result' => $result,
1378            'memo' => $memo,
1379            'lession_id' => $lessionId
1380        );
1381        echo json_encode($data);
1382        return;
1383    }
1384
1385    /**
1386     * @api {post} /teacher/api/getTransfer getTransfer()
1387     * @apiName getTransfer
1388     * @apiGroup API
1389     * @apiDescription Retrieves the lesson transfer data for a specific student.
1390     * 
1391     * @apiBody {Number} s_user_id The ID of the student whose transfer data is being retrieved.
1392     * 
1393     * @apiSampleRequest off
1394     */
1395    
1396    public function getTransfer() {
1397        $this->layout = false;
1398        if ($this->request->is('post')) {
1399            $post = $this->request->data;
1400            $user_id = (isset($post['s_user_id']))?$post['s_user_id']:'';
1401            if (empty($user_id)) {
1402                return;
1403            }
1404            $data = $this->LessonTransfer->getDataByUserId($user_id);
1405            $this->set('data', $data);
1406        } else {
1407            return;
1408        }
1409    }
1410
1411    /**
1412     * @api {post} /teacher/api/setTransfer setTransfer()
1413     * @apiName setTransfer
1414     * @apiGroup API
1415     * @apiDescription handles adding, updating, or deleting a lesson transfer based on the presence of a memo or comment.
1416     * 
1417     * @apiBody {Number} s_user_id The ID of the student.
1418     * @apiBody {Number} t_user_id The ID of the teacher.
1419     * @apiBody {String} [comment] A comment for the lesson transfer.
1420     * 
1421     * @apiSampleRequest off
1422     * 
1423     * @apiSuccess {Boolean} result Indicates if the operation was successful (true) or not (false).
1424     * @apiSuccess {String} [message] A message describing the result of the operation.
1425     * @apiSuccess {Object} [new_data] The new or updated memo data related to the student and teacher.
1426     * 
1427     * @apiErrorExample {json} Success-Response:
1428     * (Insert):
1429     *     {
1430     *       "result": true,
1431     *       "message": "LessonTransfer added successfully",
1432     *       "new_data": { ... }
1433     *     }
1434     * 
1435     * (Update):
1436     *     {
1437     *       "result": true,
1438     *       "message": "LessonTransfer updated successfully",
1439     *       "new_data": { ... }
1440     *     }
1441     * 
1442     * (Delete):
1443     *     {
1444     *       "result": true,
1445     *       "message": "LessonTransfer deleted successfully",
1446     *       "new_data": { ... }
1447     *     }
1448     * 
1449     * (Invalid Request):
1450     *     {
1451     *       "result": false,
1452     *       "message": "Invalid request"
1453     *     }
1454     * 
1455      * @apiErrorExample {js} Used in: AngularJS
1456      * Location: "webroot/js/ng/controller/student_info.js"
1457     */
1458    public function setTransfer() {
1459        $this->autoRender = false;
1460        $data['result'] = false;
1461        $post = $this->request->data;
1462        if ($this->request->is('post')) {
1463            $conditions = array(
1464                'user_id' => $post['s_user_id'],
1465                'teacher_id' => $post['t_user_id']
1466            );
1467
1468            # Get the teachers Memo base on the student
1469            $res = $this->LessonTransfer->getTeacherUserMemo($conditions);
1470
1471            # check if the res is not empty
1472            if(empty($res)){
1473                # check also the comment if not empty
1474                if(!empty($post["comment"])){
1475                    # if not the perform a loop insertion
1476                    $conditions["comment"] = $post["comment"];
1477                    $this->LessonTransfer->add($conditions);
1478
1479                    $data['result'] = true;
1480                }
1481                
1482            }else{
1483                if(!empty($post["comment"])){
1484                    $conditions["comment"] = $post["comment"];
1485                    $this->LessonTransfer->update($conditions);
1486                    $data['message'] = 'LessonTransfer update successfully';
1487                    $data['result'] = true;
1488                }else{
1489                    $this->LessonTransfer->deleteByUserIdAndTeacherId($conditions);
1490                    $data['message'] = 'LessonTransfer deleted successfully';
1491                    $data['result'] = true;
1492                }
1493            }
1494
1495            # newly data transfered
1496            $data['new_data'] = $this->LessonTransfer->getTeacherUserMemo(array(
1497                'user_id'    => $post['s_user_id'],
1498                'teacher_id' => $post['t_user_id']
1499            ));
1500        } else {
1501            $data['message'] = 'Invalid request';
1502        }
1503        return json_encode($data);
1504    }
1505
1506    /**
1507     * @api {post} /teacher/api/updateTransfer updateTransfer()
1508     * @apiName updateTransfer
1509     * @apiGroup API
1510     * @apiDescription Updates the comment for a specific lesson transfer.
1511     * 
1512     * @apiBody {Number} id The ID of the lesson transfer to update.
1513     * @apiBody {String} comment The new comment to update for the lesson transfer.
1514     * 
1515     * @apiSampleRequest off
1516     * 
1517     * @apiSuccess {Boolean} result Indicates if the update was successful.
1518     * @apiSuccess {String} message A message describing the result.
1519     * 
1520     * @apiErrorExample {json} Success-Response:
1521     *     {
1522     *       "result": true,
1523     *       "message": "LessonTransfer add success"
1524     *     }
1525     * 
1526     * (Invalid Parameters):
1527     *     {
1528     *       "result": false,
1529     *       "message": "Invalid parameters"
1530     *     }
1531     * 
1532     * (Invalid Request):
1533     *     {
1534     *       "result": false,
1535     *       "message": "Invalid request"
1536     *     }
1537     * 
1538      * @apiErrorExample {js} Used in: AngularJS
1539      * Location: "webroot/js/ng/controller/student_info.js"
1540     */
1541    public function updateTransfer() {
1542        $this->autoRender = false;
1543        $data['result'] = false;
1544        if ($this->request->is('post')) {
1545            $post = $this->request->data;
1546            $id = (isset($post['id']))?$post['id']:'';
1547            $comment = (isset($post['comment']))?$post['comment']:'';
1548            if (empty($id) || empty($comment)) {
1549                $data['message'] = 'Invalid parameters';
1550            }else{
1551                $this->LessonTransfer->read('comment', $id);
1552                if($this->LessonTransfer->saveField('comment', $comment)) {
1553                    $data['message'] = 'LessonTransfer add success';
1554                    $data['result'] = true;
1555                }
1556            }
1557        } else {
1558            $data['message'] = 'Invalid request';
1559        }
1560        echo json_encode($data);
1561        return;
1562    }
1563
1564    /**
1565     * @api {post} /teacher/api/deleteTransfer deleteTransfer()
1566     * @apiName deleteTransfer
1567     * @apiGroup API
1568     * @apiDescription sets the status of a LessonTransfer record to 0 (deleting the transfer).
1569     * 
1570     * @apiBody {Number} id The ID of the LessonTransfer to be deleted.
1571     * 
1572     * @apiSampleRequest off
1573     * 
1574     * @apiSuccess {Boolean} result Indicates if the operation was successful (true) or not (false).
1575     * @apiSuccess {String} message A message describing the result of the operation.
1576     * 
1577     * @apiErrorExample {json} Success-Response:
1578     *     {
1579     *       "result": true,
1580     *       "message": "LessonTransfer delete success"
1581     *     }
1582     * 
1583     *  (Invalid Parameters):
1584     *     {
1585     *       "result": false,
1586     *       "message": "Invalid parameters"
1587     *     }
1588     * 
1589     *  (Invalid Request):
1590     *     {
1591     *       "result": false,
1592     *       "message": "Invalid request"
1593     *     }
1594     */
1595    public function deleteTransfer() {
1596        $this->autoRender = false;
1597        $data['result'] = false;
1598        if ($this->request->is('post')) {
1599            $post = $this->request->data;
1600            $id = (isset($post['id']))?$post['id']:'';
1601            if (empty($id)) {
1602                $data['message'] = 'Invalid parameters';
1603            }else{
1604                $this->LessonTransfer->read('status', $id);
1605                if($this->LessonTransfer->saveField('status', 0)) {
1606                    $data['message'] = 'LessonTransfer delete success';
1607                    $data['result'] = true;
1608                }
1609                $data['message'] = 'LessonTransfer delete success';
1610                $data['result'] = true;
1611            }
1612        } else {
1613            $data['message'] = 'Invalid request';
1614        }
1615        echo json_encode($data);
1616        return;
1617    }
1618
1619    /**
1620     * @api {get} /teacher/api/triggerLessonNoteValidation triggerLessonNoteValidation()
1621     * @apiName triggerLessonNoteValidation
1622     * @apiGroup API
1623     * @apiDescription validates a lesson note by updating the `valid_flg` field of the record associated with the provided `chatHash`.
1624     * 
1625     * @apiBody {String} chatHash The chat hash of the lesson note to be validated.
1626     * 
1627     * @apiSampleRequest off
1628     * 
1629     * @apiSuccess {Number} success Indicates if the operation was successful (1 for success, 0 for failure).
1630     * @apiSuccess {Number} errors The number of errors (0 for success, 1 for failure).
1631     * @apiSuccess {String} message A message describing the result of the operation.
1632     * 
1633     * @apiErrorExample {json} Success-Response:
1634     *     {
1635     *       "success": 1,
1636     *       "errors": 0,
1637     *       "message": "updated"
1638     *     }
1639     * 
1640     * @apiErrorExample {json} Error-Response:
1641     *     {
1642     *       "success": 0,
1643     *       "errors": 1,
1644     *       "message": "error occured"
1645     *     }
1646     */
1647    
1648    public function triggerLessonNoteValidation() {
1649        $this->autoRender = false;
1650        if ($this->request->is(array('get', 'ajax'))) {
1651            $chatHash = $this->request->query['chatHash'];
1652
1653            if (isset($chatHash) && !empty($chatHash)) {
1654                $data = $this->UsersLessonNote->find('first', array(
1655                    'fields' => array(
1656                        'UsersLessonNote.id',
1657                        'UsersLessonNote.chat_hash'
1658                    ),
1659                    'conditions' => array(
1660                        'UsersLessonNote.chat_hash' => $chatHash
1661                    )
1662                ));
1663
1664                if ($data) {
1665                    $this->UsersLessonNote->read(null, $data['UsersLessonNote']['id']);
1666                    $this->UsersLessonNote->saveField('valid_flg', '1');
1667                    return json_encode(array('success' => 1, 'errors' => 0, 'message' => 'updated'));
1668                }
1669                return json_encode(array('success' => 0, 'errors' => 1, 'message' => 'error occured'));
1670            }
1671        }
1672    }
1673
1674    /**
1675     * @api {post} /teacher/api/message_action saveTeacherLearnings()
1676     * @apiName saveTeacherLearnings
1677     * @apiGroup API
1678     * @apiDescription allows teachers to save their lesson learnings, including various textbook-related data and progress. It handles different types of textbooks, including Callan textbooks.
1679     * 
1680     * @apiHeader {String} Content-Type application/json
1681     * @apiHeader {String} teachers_api_token Teacher's API token for authentication.
1682     * 
1683     * @apiBody {String} lessonId ID of the lesson.
1684     * @apiBody {Number} textbook_category_type The type of textbook (e.g., Callan, Non-Callan).
1685     * @apiBody {String} [topic] Lesson's topic.
1686     * @apiBody {String} [pages] Pages covered in the lesson.
1687     * @apiBody {String} [comment] Comments regarding the lesson.
1688     * @apiBody {String} [voc1] Vocabulary 1.
1689     * @apiBody {String} [voc2] Vocabulary 2.
1690     * @apiBody {String} [voc3] Vocabulary 3.
1691     * @apiBody {Number} [callan_level_check] Indicates if Callan level check is performed.
1692     * @apiBody {Number} [callanStageNum] Callan stage number.
1693     * @apiBody {String} [callanHeadWord] Headword for Callan.
1694     * @apiBody {Number} [callanRevisionMode] Callan revision mode.
1695     * @apiBody {Number} execute Indicates if the action is 'save' or 'send'.
1696     * 
1697     * @apiSuccess {String} modified The date and time of the last modification.
1698     * @apiSuccess {Number} lesson_memo_disp_flg Indicates if the lesson memo is displayed.
1699     * @apiSuccess {Object} lesson_memo The lesson memo data.
1700     * @apiSuccess {String} lesson_memo_sent_time The date and time the lesson memo was sent.
1701     * @apiSuccess {String} callan_reading_note Reading note for Callan.
1702     * @apiSuccess {Number} lesson_memo_read_flg Indicates if the lesson memo is read.
1703     * 
1704     * 
1705     * @apiErrorExample {json} Success-Response:
1706     * {
1707     *   "modified": "2024-10-24 14:00:00",
1708     *   "lesson_memo_disp_flg": 1,
1709     *   "lesson_memo": {...},
1710     *   "lesson_memo_sent_time": "2024-10-24 14:05:00",
1711     *   "callan_reading_note": "Sample reading note",
1712     *   "lesson_memo_read_flg": 0
1713     * }
1714     * 
1715      * @apiErrorExample {js} Used in: AngularJS
1716      * Location: "webroot/js/ng/controller/student_info.js"
1717      * Location: "webroot/js/ng/controller/message.js"
1718     *
1719     * @apiSampleRequest off
1720     */
1721    /*
1722    * NC-587 save teacher learnings to be shown by the user or
1723    * to save as drafts only.
1724    * created due to changes in the said issue.
1725    * the idea of saving is due to the adaptation of the old functionality
1726    */
1727    public function saveTeacherLearnings() {
1728        $this->autoRender = false;
1729        $read = false;
1730        
1731        if ($this->request->is('ajax')) {
1732
1733            $teacherId = $this->Auth->User('id');
1734            $postRaw = array_map('trim', $this->request->data);
1735            $post = self::sanitizeInputs( array( "data" => $postRaw ) );
1736            $data = array();
1737            $return = null;
1738            $json = array();
1739            $callanLastStage = null;
1740
1741        } else if ($this->request->is('post')){
1742            $this->response->type('json');
1743            $requestData = json_decode($this->request->input(), true);
1744            $token = isset($requestData['teachers_api_token']) && !empty($requestData['teachers_api_token']) ? $requestData['teachers_api_token'] : null;
1745
1746            //validations
1747            if (!$token) {
1748                $response['error']['id'] = Configure::read('error.teachers_api_token_can_not_be_empty');
1749                $response['error']['message'] = __('teachers_api_token can not be empty');
1750                return json_encode($response);
1751            }
1752            if(!isset($requestData['lessonId']) && !$requestData['lessonId']){
1753                $response['error']['id'] = Configure::read('error.invalid_lesson_id');
1754                $response['error']['message'] = __('lessonId can not be empty');
1755                return json_encode($response);
1756            }
1757            if(!isset($requestData['textbook_category_type']) && empty($requestData['textbook_category_type'])){
1758                $response['error']['id'] = Configure::read('error.invalid_textbookCategoryType');
1759                $response['error']['message'] = __('textbook_category_type can not be empty');
1760                return json_encode($response);
1761            }
1762            if(!isset($requestData['action']) && !$requestData['action']){
1763                $response['error']['id'] = Configure::read('error.message_action');
1764                $response['error']['message'] = __('action was not specified');
1765                return json_encode($response);
1766            }
1767            
1768            $teacher = $this->Teacher->getFromToken($token);
1769
1770            if(!$teacher){
1771                $response['error']['id'] = Configure::read('error.missing_teacher');
1772                $response['error']['message'] = __('invalid teacher');
1773                return json_encode($response);
1774            }
1775
1776            $teacherId = $teacher['id'];
1777            $post = self::sanitizeInputs( array( "data" => $requestData ) );
1778            $data = array();
1779            $return = null;
1780            $json = array();
1781            $callanLastStage = null;        
1782        }
1783
1784        if($post){
1785
1786            # check if the textbook category type is not set
1787            if (!isset($post['textbook_category_type']) || !isset($post['lessonId'])) {
1788                return 'false';
1789            }
1790
1791            // - check if comment is more than 1000
1792            if (
1793                isset($post['comment']) && 
1794                !empty($post['comment']) && 
1795                mb_strlen($post['comment']) > 1000 &&
1796                $this->Auth->User('counseling_flg') == 0
1797            ) {
1798                return 'false';
1799            }
1800
1801            # if the category type is not callan
1802            if ($post['textbook_category_type'] != 2 && $post['textbook_category_type'] != 5 && $post['textbook_category_type'] != 6 && $post['textbook_category_type'] != 7) {
1803
1804                if ($post['execute'] == 0) { /* bypass required fields if action is save */
1805                    /* do nothing */
1806                } else
1807                // required fields
1808                if (
1809                    (
1810                        empty($post['topic']) ||
1811                        empty($post['comment']) ||
1812                        ( empty($post['voc1']) && empty($post['voc2']) && empty($post['voc3']) )
1813                    )
1814                ) {
1815                    if($token && $teacher){
1816                        $response['error']['message'] = __('Today\'s Lesson, Page, Vocabulary( Input at least one field ) and Comment fields are required');
1817                        return json_encode($response);
1818                    }
1819                    return 'false';
1820                }
1821
1822                # set vocabulary
1823                $vocabulary = array(
1824                    strip_tags($post['voc1']),
1825                    strip_tags($post['voc2']),
1826                    strip_tags($post['voc3'])
1827                );
1828
1829                # set phrases
1830                $phrases = array(
1831                    strip_tags($post['phrase1']),
1832                    strip_tags($post['phrase2'])
1833                );
1834
1835                # set json value
1836                $json = array(
1837                    'message_5' => array(strip_tags($post['topic_user'])),
1838                    'message_6' => $vocabulary,
1839                    'message_7' => $phrases,
1840                    'message_8' => strip_tags($post['pronunciation']),
1841                    'message_9' => strip_tags($post['grammar']),
1842                    'message_10' => strip_tags($post['comment']),
1843                    'message_11' => !empty($post['pages']) ? strip_tags($post['pages']) : null,
1844                    'message_12' => strip_tags($post['textbook_type']),
1845                    'english_topic' => array(strip_tags($post['topic'])),
1846                );
1847
1848            # if the category type is callan
1849            } else if ($post['textbook_category_type'] == 2 && $post['callan_level_check'] == 0) {
1850                # verify if params exist
1851                $post['callan_reading_note'] = isset($post['callan_reading_note']) ? $post['callan_reading_note'] : '';
1852                $post['callanTextbookType'] = isset($post['callanTextbookType']) ? $post['callanTextbookType'] : '';
1853                $post['callanReadingStageNum'] = isset($post['callanReadingStageNum']) ? $post['callanReadingStageNum'] : '';
1854                $post['callanReadParagraphNum'] = isset($post['callanReadParagraphNum']) ? $post['callanReadParagraphNum'] : '';
1855                $post['callanReadLastHeadWord'] = isset($post['callanReadLastHeadWord']) ? $post['callanReadLastHeadWord'] : '';
1856                $post['callanStageNum'] = isset($post['callanStageNum']) ? $post['callanStageNum'] : '';
1857                $post['callanParagraphNum'] = isset($post['callanParagraphNum']) ? $post['callanParagraphNum'] : '';
1858                $post['callanHeadWord'] = isset($post['callanHeadWord']) ? $post['callanHeadWord'] : '';
1859                $post['callanRevisionMode'] = isset($post['callanRevisionMode']) ? $post['callanRevisionMode'] : '';
1860                $post['nextTeacherNote'] = isset($post['nextTeacherNote']) ? $post['nextTeacherNote'] : '';
1861                $post['callanReadingCheckbox'] = isset($post['callanReadingCheckbox']) ? $post['callanReadingCheckbox'] : '';
1862
1863                # Set User's Highest Callan Stage
1864                $callanLastStage = strip_tags($post['callanStageNum']);
1865                # question array
1866                $questionAnswer = array(
1867                    strip_tags($post['callanStageNum']),
1868                    strip_tags($post['callanParagraphNum']),
1869                    strip_tags($post['callanHeadWord']),
1870                    strip_tags($post['callanRevisionMode'])
1871                );
1872
1873                # reading array
1874                $reading = array(
1875                    strip_tags($post['callanReadingStageNum']),
1876                    strip_tags($post['callanReadParagraphNum']),
1877                    strip_tags($post['callanReadLastHeadWord'])
1878                );
1879
1880                # json data for callan
1881                $json = array(
1882                    'message_13' => strip_tags($post['callanTextbookType']),
1883                    'message_14' => strip_tags($post['callanChapter']),
1884                    'message_15' => $questionAnswer,
1885                    'message_16' => $reading,
1886                    'callan_reading_checkbox' => $post['callanReadingCheckbox']
1887                );
1888
1889                if ($post['execute'] == 0) { /* bypass required fields if action is save */
1890                    /* do nothing */
1891                } else
1892                # check if the important parameters are empty
1893                if (
1894                    empty($post['callanStageNum']) ||
1895                    empty($post['callanParagraphNum']) ||
1896                    empty($post['callanHeadWord']) ||
1897                    empty($post['callanRevisionMode'])
1898                ) {
1899                    return 'false';
1900                }
1901
1902            # if the category type is callan
1903            } else if ($post['textbook_category_type'] == 5 ) {
1904                # verify if params exist
1905                $post['callanTextbookType'] = isset($post['callanTextbookType']) ? $post['callanTextbookType'] : '';
1906                $post['callanReadParagraphNum'] = isset($post['callanReadParagraphNum']) ? $post['callanReadParagraphNum'] : '';
1907                $post['callanReadLastHeadWord'] = isset($post['callanReadLastHeadWord']) ? $post['callanReadLastHeadWord'] : '';
1908                $post['callanParagraphNum'] = isset($post['callanParagraphNum']) ? $post['callanParagraphNum'] : '';
1909                $post['callanHeadWord'] = isset($post['callanHeadWord']) ? $post['callanHeadWord'] : '';
1910                $post['callanRevisionMode'] = isset($post['callanRevisionMode']) ? $post['callanRevisionMode'] : '';
1911
1912                # question array
1913                $questionAnswer = array(
1914                    strip_tags($post['callanParagraphNum']),
1915                    strip_tags($post['callanHeadWord']),
1916                    strip_tags($post['callanRevisionMode'])
1917                );
1918
1919                # reading array
1920                $reading = array(
1921                    strip_tags($post['callanReadParagraphNum']),
1922                    strip_tags($post['callanReadLastHeadWord'])
1923                );
1924
1925                # json data for callan
1926                $json = array(
1927                    'message_13' => strip_tags($post['callanTextbookType']),
1928                    'message_14' => strip_tags($post['callanChapter']),
1929                    'message_15' => $questionAnswer,
1930                    'message_16' => $reading
1931                );
1932                if ($post['execute'] == 0) { /* bypass required fields if action is save */
1933                    /* do nothing */
1934                } else
1935                # check if the important parameters are empty
1936                if (
1937                    empty($post['callanParagraphNum']) ||
1938                    empty($post['callanHeadWord']) ||
1939                    empty($post['callanRevisionMode'])
1940                ) {
1941                    return 'false';
1942                }
1943            # if the category type is callan
1944            } else if ($post['textbook_category_type'] == 6 ) {
1945                # verify if params exist
1946                $post['callanTextbookType'] = isset($post['callanTextbookType']) ? $post['callanTextbookType'] : '';
1947                $post['callanReadingStageNum'] = isset($post['callanReadingStageNum']) ? $post['callanReadingStageNum'] : '';
1948                $post['callanReadParagraphNum'] = isset($post['callanReadParagraphNum']) ? $post['callanReadParagraphNum'] : '';
1949                $post['callanReadLastHeadWord'] = isset($post['callanReadLastHeadWord']) ? $post['callanReadLastHeadWord'] : '';
1950                $post['callanStageNum'] = isset($post['callanStageNum']) ? $post['callanStageNum'] : '';
1951                $post['callanParagraphNum'] = isset($post['callanParagraphNum']) ? $post['callanParagraphNum'] : '';
1952                $post['callanHeadWord'] = isset($post['callanHeadWord']) ? $post['callanHeadWord'] : '';
1953                $post['callanRevisionMode'] = isset($post['callanRevisionMode']) ? $post['callanRevisionMode'] : '';
1954
1955                # question array
1956                $questionAnswer = array(
1957                    strip_tags($post['callanStageNum']),
1958                    strip_tags($post['callanParagraphNum']),
1959                    strip_tags($post['callanHeadWord']),
1960                    strip_tags($post['callanRevisionMode'])
1961                );
1962
1963                # reading array
1964                $reading = array(
1965                    strip_tags($post['callanReadingStageNum']),
1966                    strip_tags($post['callanReadParagraphNum']),
1967                    strip_tags($post['callanReadLastHeadWord'])
1968                );
1969
1970                # json data for callan
1971                $json = array(
1972                    'message_13' => strip_tags($post['callanTextbookType']),
1973                    'message_14' => strip_tags($post['callanChapter']),
1974                    'message_15' => $questionAnswer,
1975                    'message_16' => $reading
1976                );
1977
1978                if ($post['execute'] == 0) { /* bypass required fields if action is save */
1979                    /* do nothing */
1980                } else
1981                # check if the important parameters are empty
1982                if (
1983                    empty($post['callanStageNum']) ||
1984                    empty($post['callanParagraphNum']) ||
1985                    empty($post['callanHeadWord']) ||
1986                    empty($post['callanRevisionMode'])
1987                ) {
1988                    return 'false';
1989                }
1990
1991            # if the category type is callan and for level checking
1992            } else if ($post['textbook_category_type'] == 2 && $post['callan_level_check'] == 1) {
1993                # verify if the params exists
1994                $post['callanLevelCheckTextbookType'] = isset($post['callanLevelCheckTextbookType']) ? $post['callanLevelCheckTextbookType'] : '';
1995                $post['callanLevelCheckChapter'] = isset($post['callanLevelCheckChapter']) ? $post['callanLevelCheckChapter'] : '';
1996                $post['callanLevelCheckStageNum'] = isset($post['callanLevelCheckStageNum']) ? $post['callanLevelCheckStageNum'] : '';
1997
1998                # Set User's Highest Callan Stage
1999                $callanLastStage = $post['callanLevelCheckStageNum'];
2000                # json data for callan
2001                $json = array(
2002                    'message_13' => strip_tags($post['callanLevelCheckTextbookType']),
2003                    'message_14' => strip_tags($post['callanLevelCheckChapter']),
2004                    'message_17' => strip_tags($post['callanLevelCheckStageNum']),
2005                );
2006
2007                # check if the teacher set it to send
2008                if (
2009                    isset($post['execute']) &&
2010                    $post['execute'] == 1 &&
2011                    isset($post['userId'])
2012                ) {
2013                    # set default status to not finished
2014                    $setUserCallanCheckStatus = Configure::read('callan_entry_level.not_finished');
2015
2016                    # check if level check test has finished
2017                    if ($post['callanLevelCheckStageNum'] != 'Not Finished') {
2018                        $setUserCallanCheckStatus = Configure::read('callan_entry_level.finished');
2019                    }
2020
2021                    # set user's callan level check to finished
2022                    $this->User->updateCallanLevelCheck(array(
2023                        'user_id' => $post['userId'],
2024                        'callan_level_check' => $setUserCallanCheckStatus
2025                    ));
2026                }
2027
2028                if ($post['execute'] == 0) { /* bypass required fields if action is save */
2029                    /* do nothing */
2030                } else
2031                # check if important parameter is present
2032                if (empty($post['callanLevelCheckChapter'])) {
2033                    return 'false';
2034                }
2035
2036            } else if ($post['textbook_category_type'] == 7) {
2037
2038                if ($post['execute'] == 0) { /* bypass required fields if action is save */
2039                    /* do nothing */
2040                } else
2041                # check if the important parameters are empty
2042                if (
2043                    empty($post['comment'])
2044                ) {
2045                    if($token && $teacher){
2046                        $response['error']['message'] = __('Comment is a required field!');
2047                        return json_encode($response);
2048                    } else{
2049                        return 'false';
2050                    }
2051                }
2052
2053                # set json value
2054                $json = array(
2055                    'message_10' => array(strip_tags($post['comment'] ?? "")),
2056                    'message_19' => array(strip_tags($post["recommended_textbook"] ?? "")),
2057                    'message_20' => array(strip_tags($post['category'] ?? "")),
2058                );
2059
2060            }
2061
2062            $data = array(
2063                'modified' => date('Y-m-d H:i:s'),
2064                'lesson_memo_disp_flg' => $post['execute'],
2065                'lesson_memo' => json_encode($json),
2066                'lesson_memo_sent_time' => ( $post['execute'] == 1 )? date('Y-m-d H:i:s') : null , // update sent time
2067                'callan_reading_note' => isset($post['callan_reading_note']) ? $post['callan_reading_note'] : null,
2068                'lesson_memo_read_flg' => 0
2069            );
2070
2071            if(isset($post['lessonId']) && $post['lessonId']){
2072                $this->Teacher->openDBReplica();
2073                $teacherInfo = $this->Teacher->find('first', array(
2074                    'fields' => array(
2075                        'Teacher.id',
2076                        'Teacher.name',
2077                        'Teacher.home_flg'
2078                    ),
2079                    'joins' => array(
2080                        array(
2081                            'table' => 'lesson_onairs_logs',
2082                            'alias' => 'LessonOnairsLog',
2083                            'type' => 'LEFT',
2084                            'conditions' => array(
2085                                'LessonOnairsLog.teacher_id = Teacher.id'
2086                            )
2087                        )
2088                    ),
2089                    'conditions' => array(
2090                        'LessonOnairsLog.id' => $post['lessonId']
2091                    ),
2092                    'recursive' => -1
2093                ));
2094                $this->Teacher->closeDBReplica();
2095            }
2096
2097            //check prohibited words
2098            if(is_array($teacherInfo) && !empty($teacherInfo['Teacher'])){
2099                if ($post['execute'] == 1) {
2100                    $modifiedComment = $this->ProhibitedWord->checkProhibitedWords($post['comment']);
2101                    if ($modifiedComment) {
2102                        $this->postSlack(array(
2103                            'comment' => $modifiedComment,
2104                            'userId' => $post['userId'],
2105                            'userName' => $post['userName'],
2106                            'teacherId' => $teacherInfo['Teacher']['id'],
2107                            'teacherName' => $teacherInfo['Teacher']['name'],
2108                            'homeFlag' => $teacherInfo['Teacher']['home_flg'],
2109                            'lesson_number' => isset($this->request->data['lessonNumber']) ? $this->request->data['lessonNumber'] : "",
2110                            'lesson_id' => $post['lessonId'],
2111                            'is_lesson_onair' => $post['isLessonOnair']
2112                        ));
2113                    }
2114                }
2115            }
2116
2117            //
2118            $officeIncentiveData = array();
2119
2120            // check if successfully saved
2121            try {
2122
2123                if ($post['action'] == 'update') {
2124                    unset($data['lesson_memo_sent_time']); // remain memo_sent_time
2125                    $this->SendEditMessageSlack($post['lessonId'], $teacherId, $data);
2126                }
2127
2128                if ($post['isLessonOnair']) {
2129                    # update the user lesson onair
2130                    $this->LessonOnair->clear();
2131                    $read = $this->LessonOnair->read(array('id', 'user_id', 'lesson_memo_sent_time'), $post['lessonId']);
2132                    if ($read) {
2133                        // set initial lesson data for incentive checking
2134                        $officeIncentiveData = $read['LessonOnair'] ?? array();
2135
2136                        $this->LessonOnair->clear();
2137                        $this->LessonOnair->id = $post['lessonId'];
2138                        $this->LessonOnair->set($data);
2139                        $save = $this->LessonOnair->save();
2140                    }
2141                } else {
2142                    # update the user lesson on air log
2143                    $this->LessonOnairsLog->clear();
2144                    $read = $this->LessonOnairsLog->read(array('id', 'user_id', 'lesson_memo_sent_time'), $post['lessonId']);
2145                    if ($read) {
2146                        // set initial lesson data for incentive checking
2147                        $officeIncentiveData = $read['LessonOnairsLog'] ?? array();
2148
2149                        $this->LessonOnairsLog->clear();
2150                        $this->LessonOnairsLog->id = $post['lessonId'];
2151                        $this->LessonOnairsLog->set($data);
2152                        $save = $this->LessonOnairsLog->save();
2153                    }
2154                }
2155
2156                // NC-3564 : save reading progress
2157                if ( $save && $post['textbook_category_type'] == 2 && $post['callan_level_check'] == 0 && !$post['callanReadingCheckbox'] ) {
2158
2159                    $progressData = array(
2160                        "user_id" => $post['userId'],
2161                        "stage" => $post['callanReadingStageNum'],
2162                        "paragraph_number" => $post['callanReadParagraphNum'],
2163                        "content" => $post['callanReadLastHeadWord'],
2164                        "next_teacher_note" => $post['nextTeacherNote']
2165                    );
2166                    UsersTextbookProgressTable::saveProgress($progressData);
2167                }
2168                /* Update User Callan information*/
2169                if ($callanLastStage && $save) {
2170                    $callanStageparams = array(
2171                        "user_id" => $post['userId'],
2172                        "lesson_id" => $post['lessonId'],
2173                        "stage_num" => $callanLastStage,
2174                        "isLessonOnairTable" => $post['isLessonOnair']
2175                    );
2176                    ClassRegistry::init('UsersCallanInformation')->lessonOnAirUpdateCallanStages($callanStageparams);
2177                }
2178
2179            } catch (Exception $e) {
2180                $this->log(__METHOD__ . " [Exception Error] " .  $e->getMessage(), "debug");
2181                return 'errExecute';
2182            }
2183
2184            // if first sent message, not test user and office teacher, give message incentives
2185            if(
2186                $save &&
2187                (isset($post['action']) && $post['action'] === 'send') && 
2188                ($officeIncentiveData && empty($officeIncentiveData['lesson_memo_sent_time'])) && 
2189                strpos(strtoupper($post['userName']), '%%%TEST%%%') === false
2190            ){
2191                // get teacher data
2192                $teacherData = $this->Teacher->getTeacherInfo($teacherId);
2193
2194                // init teacher data
2195                $teacherData = $teacherData['Teacher'];
2196
2197                // check if office teacher
2198                if(isset($teacherData['home_flg']) && !$teacherData['home_flg']){
2199
2200                    // set incentive params
2201                    $incentiveParams = array(
2202                        'teacher_id' => $teacherId,
2203                        'incentive_date' => $data['lesson_memo_sent_time'] ?? $data['modified'] ?? null,
2204                        'incentive_point' => 1
2205                    );
2206
2207                    // init message incentive process
2208                    $this->OfficeMessageIncentive->setTeacherIncentive($incentiveParams);
2209                }
2210
2211            }
2212
2213            # check if saving was successful
2214            if (!$save) {
2215                if($token && $teacher){
2216                    $response['result'] = 0;
2217                    $response['message'] = 'failed';
2218                }
2219                return 'false';
2220            }
2221
2222
2223
2224            # return '1' to signify that the saving was successful
2225            if($token && $teacher){
2226                $response['result'] = 1;
2227                if($post['execute'] == '1'){
2228                    if($post['action'] === "send"){
2229                        $response['message'] = 'sent';
2230                    }else{
2231                        $response['message'] = 'updated';
2232                    }
2233                }else{
2234                    $response['message'] = 'saved';
2235                }
2236                return json_encode($response);
2237            }else {
2238                return 1;
2239            }
2240            
2241        }
2242    }
2243
2244    /**
2245    * NC-2190: send changes message prev -> new message to slack
2246    */
2247    private function SendEditMessageSlack($lessonId, $teacherId, $new) {
2248        // send the changes the slack
2249        $lesson_message = new LessonMessageController();
2250        $condition = "WHERE Lesson.id = ".$lessonId;
2251        $prev = $lesson_message->getLessonOnAirsList(1, 0, null, false, $condition, $teacherId);
2252        $prevDetail = new LessonOnairTable($prev[0]['Lesson']);
2253        $arr = (array)json_decode($prevDetail->lesson_memo);
2254        $prevMessage = "";
2255
2256        // get previous(old) message
2257        if (array_key_exists('message_5', $arr)) {
2258            $rearr = myTools::getLessonNoteMessage2($arr, 'en', null, true, "\n");
2259            $prevMessage = myTools::getLessNoteMsg($rearr, "\n");
2260        } else {
2261            $prevMessage = myTools::getLessonNoteMessage(array('lessonMessage' => json_encode($arr)), "\n");
2262        }
2263
2264        $newDetail = new LessonOnairTable($new);
2265        $arr = (array)json_decode($newDetail->lesson_memo);
2266        $newMessage = "";
2267
2268        // get new(updated) message
2269        if (array_key_exists('message_5', $arr)) {
2270            $rearr = myTools::getLessonNoteMessage2($arr, 'en', null, true, "\n");
2271            $newMessage = myTools::getLessNoteMsg($rearr, "\n");
2272        } else {
2273            $newMessage = myTools::getLessonNoteMessage(array('lessonMessage' => json_encode($arr)), "\n");
2274        }
2275
2276        // remove unnecessary <br/>
2277        $prevMessage = preg_replace('/<[^>]*>/', '', $prevMessage);
2278        $newMessage = preg_replace('/<[^>]*>/', '', $newMessage);
2279
2280        // get teacher detail
2281        $teacher = $this->Teacher->find('first', array(
2282            'fields' => array('Teacher.name'),
2283            'conditions' => array(
2284                'Teacher.id' => $prevDetail->teacher_id
2285            ),
2286            'recursive' => -1
2287            )
2288        );
2289
2290        $this->mySlack->sendSlackMessageChanged(array(
2291            'teacher_id' => $prevDetail->teacher_id,
2292            'teacher_name' => $teacher['Teacher']['name'],
2293            'user_id' => $prev[0]['User']['id'],
2294            'user_name' =>$prev[0]['User']['nickname'],
2295            'prevMessage' => $prevMessage,
2296            'newMessage' => $newMessage
2297        ));
2298    }
2299
2300    
2301    /*
2302    * array required val is ok
2303    * method created for checking teacher learnings required values
2304    */
2305    private function arrReqValIsOk($array = array()) {
2306        $errCnt = 0;
2307        foreach ($array as $key => $value) {
2308            if (is_array($value)) {
2309                foreach ($value as $k =>$v) {
2310                    if (empty($v)) {
2311                        $errCnt++;
2312                    }
2313                }
2314            } else {
2315                if (empty($value)) {
2316                    $errCnt++;
2317                }
2318            }
2319        }
2320
2321        if ($errCnt > 0) {
2322            return false;
2323        } else {
2324            return true;
2325        }
2326    }
2327
2328    /**
2329     * @api {post} /teacher/api/saveTeacherMemo saveTeacherMemo()
2330     * @apiName saveTeacherMemo
2331     * @apiGroup API
2332     * @apiDescription saves the teacher memo for a student.
2333     * 
2334     * @apiBody {String} id The ID of the lesson.
2335     * @apiBody {String} memo_status The status of the memo (1 for visible, 0 for hidden).
2336     * @apiBody {String} comment The memo to save.
2337     * @apiBody {String} required The required fields.
2338     * 
2339     * @apiSuccess {Number} result The result of the operation (1 for success, 0 for failure).
2340     * 
2341     * @apiSampleRequest off
2342     */
2343    
2344    public function saveTeacherMemo() {
2345        $this->autoRender = false;
2346        if ($this->request->is('ajax')) {
2347            $response = array(
2348                'status' => 'failed' // set failed as default value
2349            );
2350            $data = $this->request->data;
2351
2352            // 必要なデータがない
2353            if(empty($data["id"])) { return; }
2354            if(!isset($data["memo_status"])) { return; }
2355
2356            $lesson_memo_disp_flg = ($data["memo_status"]==1) ? 1 : 0;
2357
2358            foreach ($data["comment"] as $key  => $value) {
2359
2360                foreach ($value as $detail_key => $detail_value) {
2361                    // 必須項目のメッセージがない(但し、SAVEの場合を除く)
2362                    if( $lesson_memo_disp_flg==1 && !empty($data["required"][$key][$detail_key]) && (int)$data["required"][$key][$detail_key] === 1 && empty($detail_value) ) {
2363                        return;
2364                    }
2365                    // escape
2366//                    $data["comment"][$key][$detail_key] = htmlspecialchars($detail_value);
2367                }
2368            }
2369
2370            $this->UsersLessonNote->set(array(
2371                'id' => $data['id'],
2372                'lesson_memo' => json_encode($data['comment']),
2373                'lesson_memo_disp_flg' => $lesson_memo_disp_flg,
2374            ));
2375            // Success!
2376            if ($this->UsersLessonNote->save()) {
2377                return 1;
2378            }
2379        }
2380    }
2381
2382    /**
2383     * @api {post} /teacher/api/setMemo setMemo()
2384     * @apiName setMemo
2385     * @apiGroup API
2386     * @apiDescription sets the teacher memo for a student.
2387     * 
2388     * @apiBody {String} t_user_id The ID of the teacher.
2389     * @apiBody {String} s_user_id The ID of the student.
2390     * @apiBody {String} lesson_id The ID of the lesson.
2391     * @apiBody {String} memo The memo to set.
2392     * 
2393     * @apiSuccess {Number} result The result of the operation (1 for success, 0 for failure).
2394     * 
2395      * @apiErrorExample {js} Used in: AngularJS
2396      * Location: "webroot/js/ng/controller/message.js"
2397      * Location: "webroot/js/ng/controller/student_info.js"
2398      * Location: "webroot/js/recruitment/ng/controllers/userMemo.js"
2399     *
2400     * @apiSampleRequest off
2401     */
2402    public function setMemo(){
2403        $this->autoRender = false;
2404        if ($this->request->is('post')) {
2405            $post = $this->request->data;
2406            $teacherId = (isset($post['t_user_id']))?$post['t_user_id']:'';
2407            $userId = (isset($post['s_user_id']))?$post['s_user_id']:'';
2408            $lessonId = (isset($post['lesson_id']))?$post['lesson_id']:'';
2409            $memo = (isset($post['memo']))?$post['memo']:'';
2410            //validating the required field
2411
2412            if (empty($teacherId) || empty($userId) || empty($lessonId) || empty($memo)) {
2413                $this->outputSetMemo();
2414            }
2415
2416            //check if teacher is exists
2417            $teacherData = $this->Teacher->findById($teacherId);
2418            if (!$teacherData) {
2419                $this->outputSetMemo();
2420            }
2421
2422            //check if user is exists
2423            $userData = $this->User->findById($userId);
2424            if (!$userData) {
2425                $this->outputSetMemo();
2426            }
2427
2428            list($classId,$chapterId) = explode('__',$lessonId);
2429            $lessonData = $this->LessonText->findByClassIdAndChapterId($classId,$chapterId);
2430            if (!$lessonData) {
2431                $this->outputSetMemo();
2432            }
2433
2434            $userclassData = $this->UsersClass->findByUserIdAndClassIdAndChapterIdAndTeacherId($userId,$classId,$chapterId,$teacherId);
2435            if (!$userclassData) {
2436                $this->outputSetMemo();
2437            } else {
2438                $this->UsersClass->read(null,$userId);
2439                $this->UsersClass->set(array(
2440                    'teacher_memo' => $memo
2441                ));
2442                $this->UsersClass->save();
2443                $this->outputSetMemo(1);
2444            }
2445        }else{
2446            $this->outputSetMemo();
2447        }
2448    }
2449
2450    private function outputSetMemo($result=0){
2451        $data = array(
2452            'result' => $result,
2453        );
2454        echo json_encode($data);
2455        return;
2456    }
2457
2458    /**
2459     * @api {get} /teacher/api/getAnnounceCount getAnnounceCount()
2460     * @apiName getAnnounceCount
2461     * @apiGroup API
2462     * @apiDescription returns the number of announcements for a teacher.
2463     * 
2464     * @apiSuccess {Number} count The number of announcements.
2465     * 
2466      * @apiErrorExample {js} Used in: AngularJS
2467      * Location: "webroot/js/ng/app.js"
2468      * Location: "webroot/js/ng/home.js"
2469      * Location: "webroot/js/ng/controller/home.js"
2470      * Location: "webroot/js/ng/controller/reservation.js"
2471      * Location: "webroot/js/recruitment/ng/controllers/services.js"
2472     *
2473     * @apiSampleRequest off
2474     */
2475    public function getAnnounceCount() {
2476        $this->autoRender = false;
2477        $curTime = date("Y-m-d H:i:00", time());
2478
2479        // get announcement counts
2480        $this->LessonSchedule->openDBReplica();
2481        $cnt = $this->LessonSchedule->find('count', array(
2482            'conditions' => array(
2483                'LessonSchedule.lesson_time >='=> $curTime,
2484                'LessonSchedule.teacher_id' => $this->Auth->user('id'),
2485                'LessonSchedule.teacher_check_flg' => 1,
2486                'LessonSchedule.status' => 1
2487            ),
2488        ));
2489        $this->LessonSchedule->closeDBReplica();
2490
2491        // return total count
2492        return $cnt;
2493    }
2494
2495    /**
2496     * @api {post} /teacher/api/getRequestCount getRequestCount()
2497     * @apiName getRequestCount
2498     * @apiGroup API
2499     * @apiDescription returns the number of lesson requests for a teacher.
2500     * 
2501     * @apiSuccess {Number} count The number of lesson requests.
2502     * 
2503      * @apiErrorExample {js} Used in: AngularJS
2504     * Location: "webroot/js/ng/app.js"
2505      * Location: "webroot/js/ng/home.js"
2506     *
2507     * @apiSampleRequest off
2508     */
2509    public function getRequestCount() {
2510        $this->autoRender = false;
2511        $curTime = date("Y-m-d H:i:00", time());
2512
2513        // get announcement counts
2514        $this->LessonSchedule->openDBReplica();
2515        $cnt = $this->LessonSchedule->find('count', array(
2516            'conditions' => array(
2517                'LessonSchedule.lesson_time >='=> $curTime,
2518                'LessonSchedule.teacher_id' => $this->Auth->user('id'),
2519                'LessonSchedule.teacher_check_flg' => 1,
2520                'LessonSchedule.status' => Configure::read('lesson_schedule.status.lesson_request')
2521            ),
2522        ));
2523        $this->LessonSchedule->closeDBReplica();
2524
2525        // return total count
2526        return $cnt;
2527    }
2528
2529    /**
2530     * @api {get} /teacher/api/getNextReservationTime getNextReservationTime()
2531     * @apiName getNextReservationTime
2532     * @apiGroup API
2533     * @apiDescription returns the time remaining until the next lesson reservation for a teacher.
2534     * 
2535     * @apiBody {Boolean} [homeBase] Indicates if the request is from home base.
2536     * @apiBody {String} [teacherId] The ID of the teacher (optional, defaults to the authenticated user).
2537     * 
2538     * @apiSuccess {Number} reserve_time Time remaining until the next lesson reservation in seconds.
2539     * @apiSuccess {Number} shift_meal_break Time remaining until the next meal break slot in seconds.
2540     * @apiSuccess {Number} meal_break_duration Duration of the current meal break slot in minutes.
2541     * @apiSuccess {Boolean} teacher_is_meal_break Indicates if the teacher is currently on a meal break.
2542     * @apiSuccess {Boolean} teacher_force_end_meal_break Indicates if the teacher is forced to end the meal break.
2543     * 
2544      * @apiErrorExample {js} Used in: AngularJS
2545      * Location: "webroot/js/ng/services.js"
2546      * Location: "webroot/js/ng/home.js"
2547      * Location: "webroot/js/ng/controller/home.js"
2548      * Location: "webroot/js/recruitment/ng/services.js"
2549     * @apiSampleRequest off
2550     */
2551    // NC-5554 : @modified add next meal break slot
2552    // called on interval, when the teacher is not standby.
2553    public function getNextReservationTime() {
2554        $this->autoRender = false;
2555
2556        $teacherId = isset($this->request->data["teacherId"]) ? $this->request->data["teacherId"] : $this->Auth->user('id');
2557        $homeFlag = isset($this->request->data["homeBase"]) && !$this->request->data["homeBase"] ? 0 : $this->Auth->user('home_flg');
2558
2559        $curTime = date("Y-m-d H:i:00", time());
2560        $return = array();
2561        $mb = null;
2562        $mealBreakStart = 0;
2563        $return['meal_break_duration'] = -1;
2564        $return["teacher_is_meal_break"] = 0;
2565        $return["teacher_force_end_meal_break"] = 0;
2566
2567        $row = $this->LessonSchedule->find('first', array(
2568            'fields' => array('LessonSchedule.lesson_time'),
2569            'conditions' => array(
2570                'LessonSchedule.status' => 1,
2571                'LessonSchedule.lesson_time >= ' => $curTime,
2572                'LessonSchedule.teacher_id' => $teacherId,
2573            ),
2574            'order' => array('LessonSchedule.lesson_time' => 'asc')
2575        ));
2576
2577        if (!$homeFlag) {
2578            if (date('i') >= 30) {
2579                $lessonTime = date("Y-m-d H:30:00", time());
2580            } else {
2581                $lessonTime = date("Y-m-d H:00:00", time());
2582            }
2583
2584            $mb = $this->ShiftWorkMealBreak->find('first', array(
2585                'fields' => array('ShiftWorkMealBreak.lesson_time'),
2586                'conditions' => array(
2587                    'ShiftWorkMealBreak.lesson_time' => $lessonTime,
2588                    'ShiftWorkMealBreak.teacher_id' => $teacherId
2589                ),
2590                'order' => array('ShiftWorkMealBreak.lesson_time' => 'asc')
2591            ));
2592            $currenTS = $this->TeacherStatus->find('first', array(
2593                'fields' => array('created'),
2594                'conditions' => array(
2595                    'status' => 4, // Break
2596                    'remarks1' => 1, // Lunch Break
2597                    'teacher_id' => $teacherId
2598                )
2599            ));
2600            // if no meal break but teacher status is still on meal break
2601            if(!$mb && !empty($currenTS['TeacherStatus']['created'])) {
2602                // NC-6644 force end meal break
2603                $return["teacher_force_end_meal_break"] = 1;
2604                $return["teacher_is_meal_break"] = 1;
2605            }
2606            if (!empty($currenTS['TeacherStatus']['created'])) {
2607
2608                $start = strtotime(date('Y-m-d H:00:00'));
2609                $end = strtotime(date('Y-m-d H:i:00'));
2610                if (date('i') >= 30) {
2611                    $start = strtotime(date('Y-m-d H:30:00'));
2612                }
2613
2614                $return['meal_break_duration'] = round(abs($end - $start) / 60);
2615                $return["teacher_is_meal_break"] = 1;
2616            }
2617        }
2618
2619        $return['reserve_time'] = '';
2620        $return['shift_meal_break'] = '';
2621
2622        if (!empty($row['LessonSchedule']['lesson_time'])) {
2623            $return['reserve_time'] = (strtotime($row['LessonSchedule']['lesson_time']) - time());
2624        }
2625        if (!empty($mb['ShiftWorkMealBreak']['lesson_time'])) {
2626            // get the next slot meal break
2627            $return['shift_meal_break'] = (strtotime($mb['ShiftWorkMealBreak']['lesson_time']) - time());
2628        }
2629
2630        return json_encode($return);
2631    }
2632
2633    /**
2634     * @api {get} /teacher/api/checkIfMealBreakTime checkIfMealBreakTime()
2635     * @apiName checkIfMealBreakTime
2636     * @apiGroup API
2637     * @apiDescription checks if the current time is scheduled for a meal break for a teacher.
2638     * 
2639     * @apiBody {Boolean} [homeBase] Indicates if the request is from home base.
2640     * @apiBody {String} [teacherId] The ID of the teacher (optional, defaults to the authenticated user).
2641     * 
2642     * @apiSuccess {Boolean} hasMealBreak Indicates if there is a meal break.
2643     * @apiSuccess {Number} timeRemaining Time remaining until the next meal break in seconds.
2644     * 
2645     * @apiErrorExample {json} Success-Response:
2646     * {
2647     *     "timeRemaining": 0,
2648     *     "hasMealBreak": true
2649     * }
2650     * 
2651      * @apiErrorExample {js} Used in: AngularJS
2652      * Location: "webroot/js/ng/app.js"
2653     *
2654     * @apiSampleRequest off
2655     */
2656    /**
2657    * NC-6314: Called when the teacher is on standby
2658    * Check and set time remaining to 0 if current slot was scheduled for mealbreak
2659    * Set time remaining to next slot if current slot was not scheduled for mealbreak
2660    */
2661    public function checkIfMealBreakTime () {
2662        $this->autoRender = false;
2663        $return = array('hasMealBreak' => false);
2664        $homeFlag = isset($this->request->data["homeBase"]) && !$this->request->data["homeBase"] ? 0 : $this->Auth->user('home_flg');
2665        if (!$homeFlag) {
2666            $teacherId = isset($this->request->data["teacherId"]) ? $this->request->data["teacherId"] : $this->Auth->user('id');
2667            $currentTime = time();
2668            $mealBreakBefore = false;
2669
2670            if (date('i') >= 30) {
2671                $lessonTime = date("Y-m-d H:30:00", $currentTime);
2672            } else {
2673                $lessonTime = date("Y-m-d H:00:00", $currentTime);
2674            }
2675
2676            // get last teacher status
2677            $data = $this->TeacherStatusLog->find('first', array(
2678                'fields' => array('status', 'remarks1'),
2679                'conditions' => array(
2680                    'teacher_id' => $teacherId,
2681                    'status <>' => 2,
2682                    'created >=' => $lessonTime,
2683                    'created <=' => date('Y-m-d H:i:s')
2684                ),
2685                'order' => 'created DESC'
2686            ));
2687
2688            // set mealbreak before to true is previous status is mealbreak
2689            if ($data) {
2690                if ($data['TeacherStatusLog']['status'] == 4 && $data['TeacherStatusLog']['remarks1'] == 1) {
2691                    $mealBreakBefore = true;
2692                }
2693            }
2694
2695            // check if currrent time was scheduled for mealbreak
2696            $count = $this->ShiftWorkMealBreak->find('count', array(
2697                'conditions' => array(
2698                    'lesson_time' => $lessonTime,
2699                    'teacher_id' => $teacherId
2700                )
2701            ));
2702
2703            if ($count && !$mealBreakBefore) {
2704                return json_encode(array(
2705                    'timeRemaining' => 0,
2706                    'hasMealBreak' => true
2707                ));
2708            }
2709
2710            return json_encode(array(
2711                'timeRemaining' => strtotime('+30 minutes ' . $lessonTime) - $currentTime,
2712                'hasMealBreak' => true
2713            ));
2714        }
2715
2716        return json_encode($return);
2717    }
2718
2719
2720    /**
2721     * @api {post} /teacher/api/setLessonMemo setLessonMemo()
2722     * @apiName setLessonMemo
2723     * @apiGroup API
2724     * @apiDescription sets a memo for a lesson by saving the user ID, teacher ID, and comment.
2725     * 
2726     * @apiBody {String} s_user_id The ID of the student user.
2727     * @apiBody {String} t_user_id The ID of the teacher user.
2728     * @apiBody {String} comment The comment to be added to the lesson memo.
2729     * 
2730     * @apiSuccess {String} 1 Memo successfully added.
2731     * @apiError -1 Missing required parameters (user_id, teacher_id, or comment).
2732     * @apiError -2 Invalid request method (not POST).
2733     * 
2734     * @apiSampleRequest off
2735     */
2736    public function setLessonMemo() {
2737        $this->autoRender = false;
2738        if ($this->request->is('post')) {
2739            $post = $this->request->data;
2740            $user_id    = (isset($post['s_user_id']))?$post['s_user_id']:'';
2741            $teacher_id = (isset($post['t_user_id']))?$post['t_user_id']:'';
2742            $comment    = (isset($post['comment']))?$post['comment']:'';
2743            if (empty($user_id) || empty($teacher_id) || empty($comment)) {
2744                echo "-1";
2745                return;
2746            }
2747            $res = $this->LessonMemo->add(array(
2748                'user_id'    => $user_id,
2749                'teacher_id' => $teacher_id,
2750                'comment'    => $comment,
2751            ));
2752            echo $res;
2753            exit;
2754        } else {
2755                echo "-2";
2756            return;
2757        }
2758    }
2759
2760    //used only in this controller
2761    public function createTeacherStatusLog($teacher_id, $workstation_id, $status_id, $teacher_current_status = null, $tcs_created = null, $break_id = null, $break_others= null) {
2762        $break_others = (is_null($break_others)) ? "TEACHER_ACTION" : $break_others;
2763        $data = array(
2764            'teacher_id' => $teacher_id,
2765            'workstation_id' => $workstation_id,
2766            'status' => $status_id,
2767            'remarks1' => $break_id,
2768            'remarks2' => $break_others,
2769            'homeFlag' => $this->Auth->User('home_flg'),
2770            'rankId' => $this->Auth->User('rank_coin_id'),
2771            'teacherCurrentStatus' => $teacher_current_status,
2772            'tcsCreated' => $tcs_created
2773        );
2774
2775        if(isset($data['status']) && $data['status'] == 2) {
2776            $this->log(__METHOD__ . '[TEACHER STATUS STANDBY] teacher_id -> ' . json_encode($teacher_id), 'error');
2777        }
2778
2779        TeacherStatusLogTable::save($data);
2780        return TeacherStatusTable::save($data);
2781    }
2782
2783    /**
2784     * @api {get} /teacher/api/getUnviewedNotice getUnviewedNotice()
2785     * @apiName getUnviewedNotice
2786     * @apiGroup API
2787     * @apiDescription returns the number of unviewed notices for a teacher.
2788     * @apiSampleRequest off
2789     * 
2790      * @apiErrorExample {js} Used in: AngularJS
2791      * Location: "webroot/js/ng/controller/home.js"
2792      * Location: "webroot/js/ng/controller/notices.js"
2793      * Location: "webroot/js/ng/app.js"
2794      * Location: "webroot/js/ng/home.js"
2795      * Location: "webroot/js/recruitment/ng/services.js"
2796     */
2797
2798    public function getUnviewedNotice() {
2799        $this->autoRender = false;
2800        return PostTable::countUnviewedPost();
2801    }
2802
2803    /**
2804     * @api {get} /teacher/api/getUnviewedRule getUnviewedRule()
2805     * @apiName getUnviewedRule
2806     * @apiGroup API
2807     * @apiDescription returns the number of unviewed rules for a teacher.
2808     * @apiSampleRequest off
2809     * 
2810     * @apiErrorExample {js} Used in: AngularJS
2811      * Location: "webroot/js/ng/controller/home.js"
2812      * Location: "webroot/js/ng/controller/rules.js"
2813      * Location: "webroot/js/ng/app.js"
2814      * Location: "webroot/js/ng/home.js"
2815      * Location: "webroot/js/recruitment/ng/services.js"
2816     */
2817    public function getUnviewedRule() {
2818        $this->autoRender = false;
2819        return RuleTable::countUnviewedRule();
2820    }
2821
2822    /**
2823     * @api {post} /teacher/api/askHelp askHelp()
2824     * @apiName askHelp
2825     * @apiGroup API
2826     * @apiDescription sets the teacher status to help requested.
2827     * @apiSampleRequest off
2828     * 
2829     * @apiBody {Boolean} askHelp Indicates if help is requested.
2830     *
2831     * @apiErrorExample {json} Success-Response:
2832     *     {
2833     *       "flag": true
2834     *     }
2835     *
2836     * @apiErrorExample {json} Error-Response:
2837     *     {
2838     *       "flag": false
2839     *     }
2840     * 
2841      * @apiErrorExample {js} Used in: AngularJS
2842     * Location: "webroot/js/ng/controller/header.js"
2843     */
2844    public function askHelp() {
2845        $this->autoRender = false;
2846        if ($this->request->is('post')) {
2847            //*NC-4966
2848            $workstation = $this->checkWorkstationSession();
2849
2850            $askHelp = $this->request->data['askHelp'];
2851            $statChoices = TeacherStatusLogTable::statChoices();
2852            $wsId = $workstation['id'];
2853            $teacherId = $this->Auth->User('id');
2854            $statusLog = TeacherStatusLogTable::getTeacherStatusByTeacher($teacherId);
2855
2856            // NC-2706
2857            $teacherCurrentStatus = isset($statusLog->status) ? $statusLog->status : null;
2858            $tcsCreated = isset($statusLog->created) ? $statusLog->created : null;
2859
2860            $flag = true;
2861            if ($statusLog->status == $statChoices['HELP']) {
2862                $flag = false;
2863            } else {
2864                if ($askHelp == 'true') {
2865                    $this->createTeacherStatusLog($teacherId, $wsId, $statChoices['HELP'], $teacherCurrentStatus, $tcsCreated);
2866                } else {
2867                    $flag = false;
2868                }
2869            }
2870            return json_encode($flag);
2871        }
2872    }
2873
2874    /**
2875     * @api {get} /teacher/api/getBreaks getBreaks()
2876     * @apiName getBreaks
2877     * @apiGroup API
2878     * @apiDescription checks if the teacher can take a break.
2879     * @apiSampleRequest off
2880     * 
2881     * @apiSuccess {Boolean} break Indicates if the teacher can take a break.
2882     * @apiSuccess {Boolean} mealBreak Indicates if the teacher can take a meal break.
2883     * @apiSuccess {Boolean} redirectNotStandby Indicates if the teacher is not on standby.
2884     * @apiSuccess {Boolean} hasOpenedSlot Indicates if the teacher has an opened slot.
2885     * 
2886     * @apiErrorExample {json} Success-Response:
2887     *     {
2888     *       "break": true,
2889     *       "mealBreak": true,
2890     *       "redirectNotStandby": false,
2891     *       "hasOpenedSlot": true
2892     *     }
2893     *
2894     * @apiErrorExample {json} Error-Response:
2895     *     {
2896     *       "break": false,
2897     *       "mealBreak": false,
2898     *       "redirectNotStandby": false,
2899     *       "hasOpenedSlot": false
2900     *     }
2901     * 
2902     * @apiErrorExample {js} Used in: AngularJS
2903     * Location: "webroot/js/ng/controller/home.js"
2904     * Location: "webroot/js/ng/app.js"
2905     * Location: "webroot/js/ng/home.js"
2906      * Location: "webroot/js/recruitment/ng/services.js"
2907     */
2908    public function getBreaks() {
2909        $this->autoRender = false;
2910        $teacher_id = $this->Auth->user('id');
2911        $breakLimit = null;
2912        $mealBreak = false;
2913        $break = false;
2914        $teacherOnBreaks = '';
2915        $hasReservation = false;
2916        //check if stealth teacher.
2917        $this->Teacher->openDBReplica();
2918        $this->Teacher->recursive = -1;
2919        $stealth = $this->Teacher->find('count', array(
2920            'conditions' => array('id' => $teacher_id, 'stealth_flg' => 1)
2921        ));
2922        $this->Teacher->closeDBReplica();
2923
2924        $minutes_now = date('i', strtotime('now'));
2925
2926        $time_start = date('H:30');
2927        $time_end = date('H:00', strtotime('+1 hour'));
2928
2929        if ($minutes_now >= 0 && $minutes_now < 30) {
2930            $time_start = date('H:00');
2931            $time_end = date('H:30');
2932        }
2933        if ($stealth) {
2934            //check if teacher can break.
2935            $break = true;
2936            $mealBreak = true;
2937        } else {
2938            // check if booked
2939
2940            // get reservation time
2941            $currentReservationTime = false;
2942            if (date("i") >= 30 && date("i") <= 56) {
2943                $currentReservationTime = date('Y-m-d H:30:00');
2944            } elseif (date("i") >= 0 && date("i") <= 26) {
2945                $currentReservationTime = date('Y-m-d H:00:00');
2946            }
2947
2948            // check if $currentReservationTime has value
2949            if ($currentReservationTime) {
2950                $hasReservation = $this->LessonSchedule->find('count', array(
2951                        'conditions' => array(
2952                                'LessonSchedule.teacher_id' => $teacher_id,
2953                                'LessonSchedule.status' => 1,
2954                                'LessonSchedule.lesson_time' => $currentReservationTime
2955                        )
2956                ));
2957            }
2958
2959            if (!$hasReservation) {
2960                $this->BreakLimit->openDBReplica();
2961                $breakLimitData = $this->BreakLimit->find('first', array(
2962                        'conditions' => array(
2963                                "TIME_FORMAT(start_time, '%H:%i') = " => $time_start,
2964                                "TIME_FORMAT(end_time, '%H:%i') = " => $time_end
2965                        ),
2966                        'fields' => array('BreakLimit.break', 'BreakLimit.meal', 'BreakLimit.start_time', 'BreakLimit.end_time', 'BreakLimit.total')
2967                ));
2968                $this->BreakLimit->closeDBReplica();
2969                if (!empty($breakLimitData)) {
2970                    $startDate = date('Y-m-d '.$breakLimitData['BreakLimit']['start_time']);
2971                    $endDate = date('Y-m-d '.$breakLimitData['BreakLimit']['end_time']);
2972                    $breakLimit = $breakLimitData['BreakLimit']['break'];
2973                    $mealBreakLimit = $breakLimitData['BreakLimit']['meal'];
2974                    $breakLimitTotal =  $breakLimitData['BreakLimit']['total'];
2975
2976                    $this->TeacherStatus->openDBReplica();
2977                    $teacherBreaks= $this->TeacherStatus->getDataSource();
2978                    $data = $teacherBreaks->fetchAll("
2979                            SELECT
2980                                SUM(CASE WHEN `ts`.`remarks1` = 1 THEN 1 ELSE 0 END) as `lunch_break`,
2981                                SUM(CASE WHEN `ts`.`remarks1` = 2 THEN 1 ELSE 0 END) as `break`
2982                            FROM `teachers` AS `t`
2983                            INNER JOIN `teacher_status` AS `ts`
2984                            ON (`t`.id = `ts`.`teacher_id` AND `t`.stealth_flg = 0)
2985                            WHERE `ts`.`status` = 4 AND `ts`.`remarks1` IN (1 , 2)
2986                                AND `ts`.`created` BETWEEN '" . $startDate . "'
2987                                AND '" . $endDate . "'"
2988                            );
2989                    $this->TeacherStatus->closeDBReplica();
2990                    $data = $data[0][0];
2991                    $teachersOnBreak = is_null($data['break']) ? 0 : $data['break'];
2992                    $teachersOnMealBreak = is_null($data['lunch_break']) ? 0 : $data['lunch_break'];
2993
2994                    // check teacher meal break limit
2995                    if ($mealBreakLimit > $teachersOnMealBreak) {
2996                        $teacherOnBreaks = $this->countTeacherOnBreaks($startDate, $endDate);
2997                        if ($breakLimitTotal > $teacherOnBreaks) {
2998                            $mealBreak = true;
2999                        }
3000                    }
3001
3002                    // check teacher break limit
3003                    if ($breakLimit > $teachersOnBreak) {
3004                        if($teacherOnBreaks == '') {
3005                            $teacherOnBreaks = $this->countTeacherOnBreaks($startDate, $endDate);
3006                        }
3007                        if ($breakLimitTotal > $teacherOnBreaks) {
3008                            $break = true;
3009                        }
3010                    }
3011                } else {
3012                    $mealBreak = true;
3013                    $break = true;
3014                }
3015            }
3016        }
3017
3018        $redirectNotStandby = false;
3019        //native teacher redirection
3020        $suddenLessonFlag = $this->TeacherRankCoin->findById($this->Auth->user('rank_coin_id'));
3021        if (isset($suddenLessonFlag['TeacherRankCoin']['sudden_lesson_flag']) && !$suddenLessonFlag['TeacherRankCoin']['sudden_lesson_flag']) {
3022            $redirectNotStandby = !(bool)$this->LessonSchedule->checkImpendingAndOngoingReservation($teacher_id);
3023        }
3024
3025        $result = array(
3026            'break' => $break,
3027            'mealBreak' => $mealBreak,
3028            'redirectNotStandby' => $redirectNotStandby,
3029            'hasOpenedSlot' =>  $this->getOpenedSlot()
3030        );
3031
3032        return json_encode($result);
3033    }
3034
3035    /**
3036     * @api {get} /teacher/api/checkCanBreak checkCanBreak()
3037     * @apiName checkCanBreak
3038     * @apiGroup API
3039     * @apiDescription checks if the current slot can open a break.
3040     * @apiSampleRequest off
3041     * 
3042     * @apiBody {String} break_id The ID of the break type.
3043     *
3044     * @apiSuccess {Boolean} can_break Indicates if the teacher can take a break.
3045     * @apiSuccess {Boolean} has_sub Indicates if the teacher is currently a substitute.
3046     * 
3047     * @apiErrorExample {json} Success-Response:
3048     *     {
3049     *       "can_break": true,
3050     *       "has_sub": false
3051     *     }
3052     *
3053     * @apiErrorExample {js} Used in: AngularJS
3054     * Location: "webroot/js/ng/controller/header.js"
3055     * Location: "webroot/js/ng/controller/schedule.js"
3056     */
3057    public function checkCanBreak() {
3058        $this->autoRender = false;
3059        $breakType = $this->request->query['break_id'];
3060        $break_time_start = date('H:i:s');
3061
3062        $minutes_now = date('i');
3063
3064        $time_start = date('H:30');
3065        $time_end = date('H:00', strtotime('+1 hour'));
3066
3067        if ($minutes_now >= 0 && $minutes_now < 30) {
3068            $time_start = date('H:00');
3069            $time_end = date('H:30');
3070        }
3071
3072        $breakLimit = null;
3073        $canBreak = false;
3074        //check if stealth teacher.
3075        $this->Teacher->recursive = -1;
3076        $stealth = $this->Teacher->find('count', array(
3077            'conditions' => array('id' => $this->Auth->user('id'), 'stealth_flg' => 1)
3078        ));
3079
3080        if ($stealth) {
3081            $result = array(
3082                'can_break' => true
3083            );
3084            return json_encode($result);
3085        }
3086        $this->BreakLimit->openDBReplica();
3087        $breakLimitInfo = $this->BreakLimit->find('first', array(
3088            'conditions' => array(
3089                "TIME_FORMAT(start_time, '%H:%i') = " => $time_start,
3090                "TIME_FORMAT(end_time, '%H:%i') = " => $time_end
3091            ),
3092            'fields' => array('BreakLimit.break', 'BreakLimit.meal', 'BreakLimit.start_time', 'BreakLimit.end_time', 'BreakLimit.total')
3093        ));
3094        $this->BreakLimit->closeDBReplica();
3095
3096        if (!empty($breakLimitInfo)) {
3097            $startDate = date('Y-m-d '.$breakLimitInfo['BreakLimit']['start_time']);
3098            $endDate = date('Y-m-d '.$breakLimitInfo['BreakLimit']['end_time']);
3099            if ($breakType == 1) {
3100                // meal break
3101                $breakLimit = $breakLimitInfo['BreakLimit']['meal'];
3102            } elseif ($breakType == 2) {
3103                // break
3104                $breakLimit = $breakLimitInfo['BreakLimit']['break'];
3105            }
3106            $breakLimitTotal =  $breakLimitInfo['BreakLimit']['total'];
3107            $countTeacherOnBreak = $this->TeacherStatus->find('count', array(
3108                'joins' => array(
3109                    array(
3110                        'table' => 'teachers',
3111                        'alias' => 'Teacher',
3112                        'type' => 'INNER',
3113                        'conditions' => array('TeacherStatus.teacher_id = Teacher.id', 'Teacher.stealth_flg = 0')
3114                    )
3115                ),
3116                'conditions' => array(
3117                    'TeacherStatus.status' => 4,
3118                    'TeacherStatus.remarks1' => $breakType,
3119                    'TeacherStatus.created BETWEEN ? AND ? ' => array($startDate, $endDate)
3120                )
3121            ));
3122            if ($breakLimit > $countTeacherOnBreak) {
3123                $countTeachersOnBreak = $this->countTeacherOnBreaks($startDate, $endDate);
3124                if ($breakLimitTotal > $countTeachersOnBreak) {
3125                    $canBreak = true;
3126                }
3127            }
3128        } else {
3129            if ($breakType == 2) {
3130                $canBreak = true;
3131            }
3132        }
3133
3134        //this will check if teacher is currently a sub after teacher auto cancellation
3135        $memcached = new myMemcached();
3136        $lessonTime = date('Y-m-d H:00:00');
3137        if (date('i') >= 30) {
3138            $lessonTime = date('Y-m-d H:30:00');
3139        }
3140        $subteacherIds = $memcached->get('auto_cancelation_substitute_'.strtotime($lessonTime));
3141        $subTeacherArray = array();
3142        $hasSubLesson = false;
3143        if ($subteacherIds) {
3144            $subTeacherArray = explode(",", $subteacherIds);
3145            $hasSubLesson = ($subTeacherArray && in_array($this->Auth->user('id'), $subTeacherArray)) ? true : false;
3146        }
3147
3148        $result = array(
3149            'can_break' => $canBreak,
3150            'has_sub' => $hasSubLesson
3151        );
3152        return json_encode($result);
3153    }
3154
3155    /**
3156     * @api {get} /teacher/api/checkCanBreakSlot checkCanBreakSlot()
3157     * @apiName checkCanBreakSlot
3158     * @apiGroup API
3159     * @apiDescription checks if the current slot can open a meal break.
3160     * @apiSampleRequest off
3161     * 
3162     * @apiBody {String} break_time_start The start time of the break.
3163     *
3164     * @apiSuccess {Boolean} can_break Indicates if the teacher can take a break.
3165     * @apiSuccess {Boolean} teacher_mb_limit Indicates if the teacher has reached the meal break limit.
3166     * 
3167     * @apiErrorExample {json} Success-Response:
3168     *     {
3169     *       "can_break": true,
3170     *       "teacher_mb_limit": false
3171     *     }
3172     *
3173     * @apiErrorExample {json} Error-Response:
3174     *     {
3175     *       "can_break": false,
3176     *       "teacher_mb_limit": true
3177     *     }
3178      * @apiErrorExample {js} Used in: AngularJS
3179      * Location: "webroot/js/ng/controller/schedule.js"
3180     */
3181    // NC-5554 : check if the slot can open a meal break
3182    public function checkCanBreakSlot () {
3183        $this->autoRender = false;
3184        $break_time_start = isset($this->request->query['break_time_start']) ? $this->request->query['break_time_start'] : NULL;
3185
3186        if ($break_time_start) {
3187
3188            $time_start = date('H:00', strtotime($break_time_start));
3189            $time_end = date('H:30', strtotime($break_time_start));
3190
3191            if (date('i', strtotime($break_time_start)) == '30') {
3192                $time_start = date('H:30', strtotime($break_time_start));
3193                $time_end = date('H:00', strtotime($break_time_start . ' +1 hour'));
3194            }
3195
3196        } else {
3197            $result = array(
3198                'can_break' => false,
3199                'teacher_mb_limit' => false
3200            );
3201            return json_encode($result);
3202        }
3203
3204        $from = date('Y-m-d 03:00:00', strtotime($break_time_start));
3205        $to = date('Y-m-d 02:59:00', strtotime($break_time_start . ' +1 days'));
3206        $time = date('H:i:s', strtotime($break_time_start));
3207
3208        if ($time == '00:00:00' || $time == '00:30:00'
3209            || $time == '01:00:00' || $time == '01:30:00'
3210            || $time == '02:00:00' || $time == '02:30:00'
3211        ) {
3212            $from = date('Y-m-d 03:00:00', strtotime($break_time_start . ' -1 days'));
3213            $to = date('Y-m-d 02:59:00', strtotime($break_time_start));
3214        }
3215
3216
3217        // check if teaher has 2 slots already for the day.
3218        $limitPerDay = $this->ShiftWorkMealBreak->find('count', array(
3219            'conditions' => array(
3220                'teacher_id' => $this->Auth->user('id'),
3221                'lesson_time >=' => $from,
3222                'lesson_time <=' => $to
3223            )
3224        ));
3225
3226        if ($limitPerDay && $limitPerDay >= Configure::read('office.mealbreak_limit')) {
3227            $result = array(
3228                'can_break' => false,
3229                'teacher_mb_limit' => true
3230            );
3231            return json_encode($result);
3232        }
3233
3234        $breakLimit = null;
3235        $canBreak = false;
3236        //check if stealth teacher.
3237        $this->Teacher->recursive = -1;
3238        $stealth = $this->Teacher->find('count', array(
3239            'conditions' => array('id' => $this->Auth->user('id'), 'stealth_flg' => 1)
3240        ));
3241
3242        if ($stealth) {
3243            $result = array(
3244                'can_break' => true,
3245                'teacher_mb_limit' => false
3246            );
3247            return json_encode($result);
3248        }
3249        $breakLimitInfo = $this->BreakLimit->find('first', array(
3250            'conditions' => array(
3251                "TIME_FORMAT(start_time, '%H:%i') = " => $time_start,
3252                "TIME_FORMAT(end_time, '%H:%i') = " => $time_end
3253            ),
3254            'fields' => array('BreakLimit.break', 'BreakLimit.meal', 'BreakLimit.start_time', 'BreakLimit.end_time', 'BreakLimit.total')
3255        ));
3256
3257        if (!empty($breakLimitInfo)) {
3258            $startDate = date('Y-m-d '.$breakLimitInfo['BreakLimit']['start_time']);
3259            $endDate = date('Y-m-d '.$breakLimitInfo['BreakLimit']['end_time']);
3260
3261            // meal break
3262            $breakLimit = $breakLimitInfo['BreakLimit']['meal'];
3263
3264            $breakLimitTotal =  $breakLimitInfo['BreakLimit']['total'];
3265            $countTeacherOnBreak = $this->ShiftWorkMealBreak->find('count', array(
3266                'conditions' => array('ShiftWorkMealBreak.lesson_time' => $break_time_start)
3267            ));
3268            if ($breakLimit > $countTeacherOnBreak) {
3269                $countTeachersOnBreak = $this->countTeacherOnBreaks($startDate, $endDate);
3270                if ($breakLimitTotal > $countTeachersOnBreak) {
3271                    $canBreak = true;
3272                }
3273            }
3274        }
3275
3276        $result = array(
3277            'can_break' => $canBreak,
3278            'teacher_mb_limit' => false
3279        );
3280        return json_encode($result);
3281    }
3282
3283    private function countTeacherOnBreaks($startDate = null, $endDate = null) {
3284        $this->TeacherStatus->openDBReplica();
3285        $countTeachersOnBreak = $this->TeacherStatus->find('count', array(
3286            'joins' => array(
3287                array(
3288                    'table' => 'teachers',
3289                    'alias' => 'Teacher',
3290                    'type' => 'LEFT',
3291                    'conditions' => array('TeacherStatus.teacher_id = Teacher.id')
3292                )
3293            ),
3294            'conditions' => array(
3295                'TeacherStatus.status' => '4',
3296                'TeacherStatus.remarks1' => array('1','2'),
3297                'TeacherStatus.created >= ' => $startDate,
3298                'TeacherStatus.created <= ' => $endDate,
3299                'Teacher.stealth_flg' => "0"
3300            )
3301        ));
3302        $this->TeacherStatus->closeDBReplica();
3303        return $countTeachersOnBreak;
3304    }
3305
3306    /**
3307     * @api {get} /teacher/api/getReservedOrAvailableOnMealBreak getReservedOrAvailableOnMealBreak()
3308     * @apiName getReservedOrAvailableOnMealBreak
3309     * @apiGroup API
3310     * @apiDescription checks if the teacher has a reservation or is available within the meal break time.
3311     * @apiSampleRequest off
3312     * 
3313     * @apiSuccess {Boolean} notify Indicates if the teacher has a reservation or is available within the meal break time.
3314     * 
3315     * @apiErrorExample {json} Success-Response:
3316     *     {
3317     *       "notify": true
3318     *     }
3319     * 
3320      * @apiErrorExample {js} Used in: AngularJS
3321      * Location: "webroot/js/ng/controller/header.js"
3322     */
3323    /**
3324     * Check if teacher has reservaton or is available within meal break time.
3325     */
3326    public function getReservedOrAvailableOnMealBreak() {
3327        $this->autoRender = false;
3328        if (date('i') <= 30) {
3329            $startDate = date('Y-m-d H:30:00');
3330        } else {
3331            $startDate = date('Y-m-d H:00:00', strtotime('+1 hour'));
3332        }
3333
3334        $endDate = date('Y-m-d H:i:00', strtotime('+1 hour', strtotime($startDate)));
3335        $this->ShiftWorkOn->openDBReplica();
3336        $slots = $this->ShiftWorkOn->find('count', array(
3337            'conditions' => array(
3338                'ShiftWorkOn.teacher_id' => $this->Auth->user('id'),
3339                'ShiftWorkOn.lesson_time >=' => $startDate,
3340                'ShiftWorkOn.lesson_time <' => $endDate
3341            )
3342        ));
3343        $this->ShiftWorkOn->closeDBReplica();
3344        // if has shift available
3345        if ($slots) {
3346            return json_encode(array('notify' => true));
3347        }
3348        return json_encode(array('notify' => false));
3349    }
3350
3351    /**
3352     * @api {get} /teacher/api/canLesson canLesson()
3353     * @apiName canLesson
3354     * @apiGroup API
3355     * @apiDescription checks if the teacher can lesson.
3356     * @apiSampleRequest off
3357     * 
3358     * @apiSuccess {Boolean} canLesson Indicates if the teacher can lesson.
3359     * 
3360     * @apiErrorExample {json} Success-Response:
3361     *     {
3362     *       "canLesson": true
3363     *     }
3364     *
3365     * @apiErrorExample {json} Error-Response:
3366     *     {
3367     *       "canLesson": false
3368     *     }
3369     */
3370    /**
3371     * Check if teacher can lesson
3372     */
3373    public function canLesson() {
3374        $this->autoRender = false;
3375        $data = $this->LessonOnair->find('first', array(
3376            'fields' => array('connect_flg', 'status'),
3377            'conditions' => array('teacher_id' => $this->Auth->user('id')),
3378            'recursive' => -1
3379        ));
3380
3381        if(!isset($data['LessonOnair'])) {
3382            return json_encode(array('canLesson' => true));
3383        }
3384        else if ($data['LessonOnair']['status'] == 3 || $data['LessonOnair']['connect_flg'] == 1) {
3385            return json_encode(array('canLesson' => true));
3386        } else {
3387            return json_encode(array('canLesson' => false));
3388        }
3389    }
3390
3391    /**
3392     * @api {post} /teacher/api/studentDisconnection studentDisconnection()
3393     * @apiName studentDisconnection
3394     * @apiGroup API
3395     * @apiDescription will be called whenever the teacher is in the ongoing lesson state.
3396     * @apiSampleRequest off
3397     * 
3398     * @apiBody {String} chatHash The chat hash.
3399     *
3400     * @apiSuccess {boolean} error Indicates if there is an error.
3401     * @apiSuccess {String} content The content of the response.
3402     * 
3403     * @apiErrorExample {json} Success-Response:
3404     *     {
3405     *       "error": false,
3406     *       "content": ""
3407     *     }
3408     *
3409     * @apiErrorExample {json} Error-Response:
3410     *     {
3411     *       "error": true,
3412     *       "content": "invalid_request"
3413     *     }
3414     */
3415    /**
3416     * this function will be called
3417     * whenever the teacher is in the ongoing lesson state
3418     */
3419    public function studentDisconnection(){
3420        $this->autoRender = false;
3421
3422        // only allow ajax requests
3423        if (!$this->request->is('ajax')) {
3424            return json_encode(array('error' => true, 'content' => 'invalid_request'));
3425        }
3426
3427        // set post data
3428        $post = $this->request->data;
3429
3430        // check if chathash exists
3431        if (!isset($post['chatHash'])) {
3432            return json_encode(array('error' => true, 'content' => 'invalid_params'));
3433        }
3434
3435        // get vars
3436        $chatHash = $post['chatHash'];
3437
3438        // declare myMemcached
3439        $memcached = new myMemcached();
3440
3441        // set
3442        $memcached->set(array('key' => 'DC_' . $chatHash, 'value' => time(), 'expire' => 3600 ));
3443
3444        // return reservation
3445        return json_encode(array('error' => false, 'content' => ''));
3446    }
3447
3448    /**
3449     * @api {get} /teacher/api/setValidLesson setValidLesson()
3450     * @apiName setValidLesson
3451     * @apiGroup API
3452     * @apiDescription updates the validity flag of the lesson.
3453     * @apiSampleRequest off
3454     * 
3455     * @apiBody {String} chat_hash The chat hash of the lesson.
3456     * @apiBody {Boolean} valid_flg The validity flag to set.
3457     *
3458     * @apiSuccess {boolean} result Indicates if the operation was successful.
3459     * @apiSuccess {String} message The message of the response.
3460     * 
3461     * @apiErrorExample {json} Success-Response:
3462     *     {
3463     *       "result": true,
3464     *       "message": "lesson_memo_valid_flg has changed"
3465     *     }
3466     *
3467     * @apiErrorExample {json} Error-Response:
3468     *     {
3469     *       "result": false,
3470     *       "message": "lesson on air not found"
3471     *     }
3472     */
3473    /**
3474     * Update valid_flg of onairs
3475     */
3476    public function setValidLesson() {
3477        $this->autoRender = false;
3478
3479        $data = $this->request->data;
3480        $lesson = $this->LessonOnair->find('first', array(
3481            'fields' => array('id'),
3482            'conditions' => array('chat_hash' => $data['chat_hash'])
3483        ));
3484
3485        $res['result'] = false;
3486        if (empty($lesson['LessonOnair'])) {
3487            $res['message'] = 'lesson on air not found';
3488        } else {
3489            $res['result'] = true;
3490            $res['message'] = 'lesson_memo_valid_flg has changed';
3491            $this->LessonOnair->read(null, $lesson['LessonOnair']['id']);
3492            $this->LessonOnair->set('lesson_memo_valid_flg', $data['valid_flg']);
3493            $this->LessonOnair->save();
3494        }
3495        return json_encode($res);
3496    }
3497
3498    /**
3499     * @api {get} /teacher/api/countUnreadAppreciationMessage countUnreadAppreciationMessage()
3500     * @apiName countUnreadAppreciationMessage
3501     * @apiGroup API
3502     * @apiDescription counts the unread appreciation message.
3503     * @apiSampleRequest off
3504     * 
3505     * @apiSuccess {int} countUnread The count of unread appreciation message.
3506     * 
3507     * @apiErrorExample {json} Success-Response:
3508     *     {
3509     *       "countUnread": 5
3510     *     }
3511     *
3512     * @apiErrorExample {json} Error-Response:
3513     *     {
3514     *       "countUnread": 0
3515     *     }
3516     * 
3517      * @apiErrorExample {js} Used in: AngularJS
3518      * Location: "webroot/js/ng/controller/home.js"
3519      * Location: "webroot/js/ng/app.js"
3520      * Location: "webroot/js/ng/home.js"
3521     */
3522    /**
3523     * Count unread appreciation message 
3524     * @return int countmessage
3525     */
3526
3527    public function countUnreadAppreciationMessage(){
3528        $this->autoRender = false;
3529
3530        $teacherId = $this->Auth->user('id');
3531
3532        //count unread appreciation 
3533        $countUnread = $this->TeacherCoinBox->countUnreadAppreciation($teacherId);
3534
3535        return $countUnread;
3536    }
3537
3538    /**
3539     * @api {post} /teacher/api/message_badge countUnsentMessage()
3540     * @apiName countUnsentMessage
3541     * @apiGroup API
3542     * @apiDescription counts the unsent message / drafts.
3543     * @apiSampleRequest off
3544     * 
3545     * @apiBody {String} teachers_api_token The API token of the teacher.
3546     *
3547     * @apiSuccess {int} badge The count of unsent message / drafts.
3548     * 
3549     * @apiErrorExample {json} Success-Response:
3550     *     {
3551     *       "badge": 5
3552     *     }
3553     * 
3554      * @apiErrorExample {js} Used in: AngularJS
3555      * Location: "webroot/js/ng/app.js"
3556     */
3557    /**
3558     * Count unsent message / drafts
3559     * @return int countdraft
3560     */
3561    public function countUnsentMessage() {
3562        $this->autoRender = false;
3563
3564        if ($this->request->is('post')) {
3565
3566            $data = json_decode($this->request->input(), true);
3567
3568            if($data){
3569                $teacher = $this->Teacher->getFromToken($data['teachers_api_token']);
3570
3571                if(empty($teacher)){
3572                    $response['error']['id'] = Configure::read('error.missing_teacher');
3573                    $response['error']['message'] = __('invalid teacher');
3574                    return json_encode($response);
3575                }
3576
3577                $onairsDrafts = $this->LessonOnair->countDrafts($teacher['id']);
3578                $onairsLogDrafts = $this->LessonOnairsLog->countDrafts($teacher['id']);
3579                $countDrafts = $onairsDrafts + $onairsLogDrafts;
3580                
3581                return json_encode(['badge' => $countDrafts]);
3582            } else {
3583
3584                $teacher_id = $this->Auth->user('id');
3585                // count unsent messages
3586                $onairsDrafts = $this->LessonOnair->countDrafts($teacher_id);
3587                $onairsLogDrafts = $this->LessonOnairsLog->countDrafts($teacher_id);
3588                $countDrafts = $onairsDrafts + $onairsLogDrafts;
3589    
3590            }
3591            
3592        } 
3593        
3594        return $countDrafts;
3595    }
3596
3597    /**
3598     * @api {post} /teacher/api/getlastCallanStage getlastCallanStage()
3599     * @apiName getlastCallanStage
3600     * @apiGroup API
3601     * @apiDescription gets the last callan stage number of user and teacher.
3602     * @apiSampleRequest off
3603     * 
3604     * @apiBody {String} user_id The ID of the user.
3605     *
3606     * @apiSuccess {boolean} result Indicates if the operation was successful.
3607     * @apiSuccess {int} last_callan_stage The last callan stage number.
3608     * @apiSuccess {String} message The message of the response.
3609     * 
3610     * @apiErrorExample {json} Success-Response:
3611     *     {
3612     *       "result": true,
3613     *       "last_callan_stage": 3
3614     *     }
3615     *
3616     * @apiErrorExample {json} Error-Response:
3617     *     {
3618     *       "result": false,
3619     *       "message": "missing required values"
3620     *     }
3621     * 
3622      * @apiErrorExample {js} Used in: AngularJS
3623      * Location: "webroot/js/ng/app.js"
3624     */
3625    /**
3626     * gets the last callan stage number of user and teacher
3627     */
3628    public function getlastCallanStage() {
3629        $this->autoRender = false;
3630
3631        $data = $this->request->data;
3632        if (empty($data) || empty($data['user_id'])) {
3633            return json_encode(array('result' => false, 'message' => 'missing required values'));
3634        }
3635        $lastCallanStage = false;
3636
3637        $regCallanParams = array( 'userId' => $data['user_id'] ,'callanType' => 2,'control_field' => "callan_info");
3638        $result = $this->LessonOnairsLog->latestCallanLesson($regCallanParams);
3639        // if last callan stage does not exist
3640        $res = array('result' => false, 'last_callan_stage' => 0);
3641        if (empty($result)) {
3642            $res['message'] = 'no result found';
3643        } else {
3644            // parse
3645            $jsonLastCallan = json_decode($result['LessonMessage']['lesson_memo']);
3646            $lastCallanStage = 0;
3647            // if last staging is set
3648            if (isset($jsonLastCallan->message_15[0]) && $jsonLastCallan->message_15[0] != "Not Finished") {
3649                $lastCallanStage = explode(" ", $jsonLastCallan->message_15[0]);
3650                $lastCallanStage = isset($lastCallanStage[1]) ? intVal($lastCallanStage[1]) : 0;
3651            } else if (isset($jsonLastCallan->message_17) && $jsonLastCallan->message_17 != "Not Finished") {
3652                $lastCallanStage = explode(" ", $jsonLastCallan->message_17);
3653                $lastCallanStage = isset($lastCallanStage[1]) ? intVal($lastCallanStage[1]) : 0;
3654            }
3655            $res['last_callan_stage'] = $lastCallanStage;
3656            $res['result'] = true;
3657        }
3658        return json_encode($res);
3659    }
3660
3661    /**
3662     * @api {get} /teacher/api/callStartTeacherApi callStartTeacherApi()
3663     * @apiName callStartTeacherApi
3664     * @apiGroup API
3665     * @apiDescription initializes the lesson start.
3666     * @apiSampleRequest off
3667     * 
3668     * @apiBody {String} chatHash The chat hash.
3669     * @apiBody {Boolean} isIncludeLessonCount Whether to include the lesson count.
3670     * @apiBody {String} studentLessonLocalizeDir The student's lesson localization directory.
3671     *
3672     * @apiSuccess {boolean} error Indicates if there is an error.
3673     * @apiSuccess {Object} content The content of the response.
3674     * @apiSuccess {String} content.studentImage The student's image URL.
3675     * @apiSuccess {String} content.studentName The student's name.
3676     * @apiSuccess {int} content.studentLessonCount The student's lesson count.
3677     * @apiSuccess {int} content.lessonType The lesson type.
3678     * 
3679     * @apiErrorExample {json} Success-Response:
3680     *     {
3681     *       "error": false,
3682     *       "content": {
3683     *         "studentImage": "image_url",
3684     *         "studentName": "Student Name",
3685     *         "studentLessonCount": 5,
3686     *         "lessonType": 1,
3687     *         ...
3688     *       }
3689     *     }
3690     *
3691     * @apiErrorExample {json} Error-Response:
3692     *     {
3693     *       "error": true,
3694     *       "content": "Error message"
3695     *     }
3696     */
3697    /**
3698     * initialize lesson start
3699     */
3700    public function callStartTeacherApi () {
3701        $this->autoRender = false;
3702        $hasLessonCount = 0;//prevent warning
3703
3704        // - default language
3705        $defaultLanguage = Configure::read('english_language_id');
3706        $engLanguage = "en";
3707        
3708        // return view vars
3709        $viewVars = array(
3710            'studentImage' => "",
3711            'studentName' => "Missing No.",
3712            'studentLessonCount' => 0,
3713            'lessonType' => 1
3714        );
3715
3716        // only allow ajax requests
3717        if (!$this->request->is('ajax')) {
3718            return json_encode(array('error' => true, 'content' => 'invalid_request'));
3719        }
3720
3721        // set post data
3722        $post = $this->request->data;
3723
3724        // check if chathash exists
3725        if (!isset($post['chatHash'])) {
3726            return json_encode(array('error' => true, 'content' => 'invalid_params'));
3727        }
3728
3729        // get vars
3730        $chatHash = $post['chatHash'];
3731        $hasLessonCount = isset($post['isIncludeLessonCount']) ? $post['isIncludeLessonCount'] : null;
3732        $userLocale = (isset($post['studentLessonLocalizeDir']) && $post['studentLessonLocalizeDir']) ? $post['studentLessonLocalizeDir'] : Configure::read('default.user_language');
3733
3734        // get lessononairs
3735        $lessonOnair = $this->LessonOnair->find('first', array(
3736            'fields' => array(
3737                'LessonOnair.id',
3738                'LessonOnair.chat_hash',
3739                'LessonOnair.user_id',
3740                'LessonOnair.teacher_id',
3741                'LessonOnair.lesson_type',
3742                'LessonOnair.lesson_finish',
3743                'LessonOnair.user_agent',
3744                'LessonOnair.connect_id',
3745                'LessonOnair.requested_lesson_time',
3746                'LessonOnair.lesson_schedule_id',
3747                'LessonRequest.self_introduction',
3748                'LessonRequest.incorrect_grammar',
3749                'LessonRequest.other_request',
3750                'TextbookSubCategory.id',
3751                'TextbookSubCategory.name',
3752                'TextbookSubCategory.english_name',
3753                'GlobalTextbookSubCategory.gl_name',
3754                'Textbook.id',
3755                'Textbook.name',
3756                'Textbook.name_eng',
3757                'Textbook.chapter_id',
3758                'Textbook.main_topic_id',
3759                'GlobalTextbook.gl_name',
3760                'TextbookCategory.textbook_category_type',
3761                'TextbookCategory.id',
3762                'TextbookCategory.type_id',
3763                'TextbookCategory.name',
3764                'TextbookCategory.english_name',
3765                'TextbookCategory.audio_acquisition_flg',
3766                'GlobalTextbookCategory.gl_name',
3767                'LessonOnair.connect_id',
3768                'LessonOnair.textbook_language',
3769                'User.native_language2',
3770                'LessonOnair.chocotto_camp_lesson_flg'
3771            ),
3772            'conditions' => array(
3773                'chat_hash' => $chatHash
3774            ),
3775            'joins' => array (
3776                array(
3777                    'type' => 'LEFT',
3778                    'table' => 'lesson_requests',
3779                    'alias' => 'LessonRequest',
3780                    'conditions' => 'LessonOnair.user_id = LessonRequest.user_id'
3781                ),
3782                array(
3783                    'type' => 'LEFT',
3784                    'table' => 'textbook_connects',
3785                    'alias' => 'TextbookConnect',
3786                    'conditions' => 'LessonOnair.connect_id = TextbookConnect.id'
3787                    ),
3788                array(
3789                    'type' => 'LEFT',
3790                    'table' => 'textbook_subcategories',
3791                    'alias' => 'TextbookSubCategory',
3792                    'conditions' => 'TextbookConnect.subcategory_id = TextbookSubCategory.id'
3793                ),
3794                array(
3795                    'type' => 'LEFT',
3796                    'table' => 'global_textbook_subcategories',
3797                    'alias' => 'GlobalTextbookSubCategory',
3798                    'conditions' => 'TextbookSubCategory.id = GlobalTextbookSubCategory.textbook_subcategory_id AND GlobalTextbookSubCategory.language_id = ' . $defaultLanguage
3799                ),
3800                array(
3801                    'type' => 'LEFT',
3802                    'table' => 'textbooks',
3803                    'alias' => 'Textbook',
3804                    'conditions' => 'TextbookConnect.textbook_id = Textbook.id'
3805                ),
3806                array(
3807                    'type' => 'LEFT',
3808                    'table' => 'global_textbooks',
3809                    'alias' => 'GlobalTextbook',
3810                    'conditions' => 'Textbook.id = GlobalTextbook.textbook_id AND GlobalTextbook.language_id = ' . $defaultLanguage
3811                ),
3812                array(
3813                    'type' => 'LEFT',
3814                    'table' => 'textbook_categories',
3815                    'alias' => 'TextbookCategory',
3816                    'conditions' => 'TextbookConnect.category_id = TextbookCategory.id'
3817                ),
3818                array(
3819                    'type' => 'LEFT',
3820                    'table' => 'global_textbook_categories',
3821                    'alias' => 'GlobalTextbookCategory',
3822                    'conditions' => 'TextbookCategory.id = GlobalTextbookCategory.textbook_category_id AND GlobalTextbookCategory.language_id = ' . $defaultLanguage
3823                ),
3824                array(
3825                    'type' => 'LEFT',
3826                    'table' => 'users',
3827                    'alias' => 'User',
3828                    'conditions' => array( 'User.id = LessonOnair.user_id' )
3829                )
3830            ),
3831            'recursive' => -1
3832        ));
3833
3834        // check if chatHash is related to a legit lessonOnairs
3835        if (!$lessonOnair) {
3836            return json_encode(array('error' => true, 'content' => 'empty_lesson_onairs'));
3837        }
3838
3839        $pcDeviceType = "PC";
3840        //get user device
3841        $studentDeviceType = myTools::getUsersDevice($lessonOnair['LessonOnair']['user_agent']); 
3842        //for mobile lesson
3843        //if device type is not a PC use student language to get the global translation
3844        if ((isset($studentDeviceType) && $studentDeviceType) && $pcDeviceType != $studentDeviceType) {
3845            $userLocale = (isset($lessonOnair['User']['native_language2']) && $lessonOnair['User']['native_language2']) ? $lessonOnair['User']['native_language2'] : 'en';
3846        } else {
3847            if ( isset($lessonOnair['LessonOnair']['textbook_language']) && $lessonOnair['LessonOnair']['textbook_language'] ) {
3848                $userLocale = $lessonOnair['LessonOnair']['textbook_language'];
3849            }
3850        }
3851
3852        // - last lesson
3853        $userLastLesson = $this->LessonOnairsLog->query("
3854            SELECT
3855                -- main fields
3856                `LessonOnairsLog`.`id`,
3857                `LessonOnairsLog`.`end_time`,
3858                `TextbookConnect`.`id`,
3859                `TextbookCategory`.`name`,
3860                `TextbookCategory`.`english_name`,
3861                `TextbookSubCategory`.`name`,
3862                `Textbook`.`id`,
3863                `Textbook`.`name`,
3864                `TextbookSubCategory`.`english_name`,
3865                `Textbook`.`name_eng`,
3866                `Textbook`.`main_topic_id`,
3867                `GlobalTextbookCategory`.`gl_name`,
3868                `GlobalTextbookSubCategory`.`gl_name`,
3869                `GlobalTextbook`.`gl_name`,
3870
3871                -- count lesson
3872                ((
3873                    SELECT
3874                        COUNT(`LessonLog`.`id`)
3875                    FROM
3876                        lesson_onairs_logs AS LessonLog
3877                    WHERE
3878                        `LessonLog`.`user_id` = `LessonOnairsLog`.`user_id`
3879                        AND `LessonLog`.`teacher_id` = `LessonOnairsLog`.`teacher_id`
3880                        AND `LessonLog`.`start_time` IS NOT NULL
3881                        AND `LessonLog`.`end_time` IS NOT NULL
3882                        AND `LessonLog`.`connect_id` IS NOT NULL
3883                )) AS `count_lesson`
3884
3885            -- main table
3886            FROM
3887                `english`.`lesson_onairs_logs` AS `LessonOnairsLog` FORCE INDEX (user_id)
3888
3889            -- join conditions
3890            LEFT JOIN
3891            `english`.`textbook_connects` AS `TextbookConnect` ON (`LessonOnairsLog`.`connect_id` = `TextbookConnect`.`id`)
3892            LEFT JOIN
3893            `english`.`textbook_categories` AS `TextbookCategory` ON (`TextbookConnect`.`category_id` = `TextbookCategory`.`id`)
3894            LEFT JOIN
3895            `english`.`textbook_subcategories` AS `TextbookSubCategory` ON (`TextbookConnect`.`subcategory_id` = `TextbookSubCategory`.`id`)
3896            LEFT JOIN
3897            `english`.`textbooks` AS `Textbook` ON (`TextbookConnect`.`textbook_id` = `Textbook`.`id`)
3898            LEFT JOIN
3899            `english`.`global_textbook_categories` AS `GlobalTextbookCategory` ON (
3900                `TextbookCategory`.`id` = `GlobalTextbookCategory`.`textbook_category_id`
3901                AND `GlobalTextbookCategory`.`language_id` = " . $defaultLanguage . "
3902            )
3903            LEFT JOIN
3904            `english`.`global_textbook_subcategories` AS `GlobalTextbookSubCategory` ON (
3905                `TextbookSubCategory`.`id` = `GlobalTextbookSubCategory`.`textbook_subcategory_id`
3906                AND `GlobalTextbookSubCategory`.`language_id` = " . $defaultLanguage . "
3907            )
3908            LEFT JOIN
3909            `english`.`global_textbooks` AS `GlobalTextbook` ON (
3910                `Textbook`.`id` = `GlobalTextbook`.`textbook_id`
3911                AND `GlobalTextbook`.`language_id` = " . $defaultLanguage . "
3912            )
3913
3914            -- where conditions
3915            WHERE
3916                `LessonOnairsLog`.`user_id` = " . $lessonOnair['LessonOnair']['user_id'] . "
3917                AND `LessonOnairsLog`.`teacher_id` = " . $this->Auth->user('id') . "
3918                AND `LessonOnairsLog`.`connect_id` IS NOT NULL
3919                AND `LessonOnairsLog`.`start_time` IS NOT NULL
3920                AND `LessonOnairsLog`.`end_time` IS NOT NULL
3921            ORDER BY `LessonOnairsLog`.`end_time` DESC
3922            LIMIT 1
3923        ");
3924        
3925
3926        // - get result - [0] index
3927        $userLastLesson = $userLastLesson[0];
3928
3929        $dataLastLesson = $this->LessonOnairsLog->userLastLessons($lessonOnair['LessonOnair']['user_id'], $lessonOnair['LessonOnair']['connect_id']);
3930        $viewVars['display_get_request'] = $this->LessonSchedule->userCurrentLessonSchedule($lessonOnair['LessonOnair']['user_id'], $lessonOnair['LessonOnair']['lesson_schedule_id']);
3931        $viewVars['lesson_memo_disp_flg'] = isset($dataLastLesson['LessonOnairsLog']['lesson_memo_disp_flg']) ? $dataLastLesson['LessonOnairsLog']['lesson_memo_disp_flg'] : '';
3932        $viewVars['lesson_memo'] = isset($dataLastLesson['LessonOnairsLog']['lesson_memo']) ? $dataLastLesson['LessonOnairsLog']['lesson_memo'] : '';
3933        $viewVars['user_id'] = isset($dataLastLesson['LessonOnairsLog']['user_id']) ? $dataLastLesson['LessonOnairsLog']['user_id'] : '';
3934
3935        $memcached = new myMemcached();
3936        $textbookNamesCached = $memcached->get(Configure::read('textbook_names_cache_key'));
3937        $order = (isset($textbookNamesCached[$lessonOnair['LessonOnair']['connect_id']]['order']) && $textbookNamesCached[$lessonOnair['LessonOnair']['connect_id']]['order']) ? $textbookNamesCached[$lessonOnair['LessonOnair']['connect_id']]['order'] : '';
3938        $orderLastLesson = (isset($textbookNamesCached[$userLastLesson['TextbookConnect']['id']]['order']) && $textbookNamesCached[$userLastLesson['TextbookConnect']['id']]['order']) ? $textbookNamesCached[$userLastLesson['TextbookConnect']['id']]['order'] : '';
3939
3940        // GET GLOBAL TRANSLATION OF TEXTBOOK, CATEGORY AND SUBCATEGORY DATA
3941        $glTextbook = GlobalTextbookTable::getGlobalData($lessonOnair['Textbook']['id']);
3942        $glTextbookCategory = ClassRegistry::init('GlobalTextbookCategoryTable')->getGlobalData($lessonOnair['TextbookCategory']['id']);
3943        $glTextbookSubcategory = ClassRegistry::init('GlobalTextbookSubcategoryTable')->getGlobalData($lessonOnair['TextbookSubCategory']['id']);
3944
3945        // - set variables
3946        // SET TEXTBOOK AND SUBCATEGORY TITLE BY USING STUDENT LANGUAGE
3947        $textBookChapterName = $lessonOnair['Textbook']['name'];
3948        if ($userLocale != Configure::read('default.user_language')) {
3949            if ($userLocale != $engLanguage) {
3950                if (isset($glTextbook[$userLocale]['gl_name']) && $glTextbook[$userLocale]['gl_name']) {
3951                    $textBookChapterName = $glTextbook[$userLocale]['gl_name'];
3952                } else {
3953                    $textBookChapterName = $lessonOnair['Textbook']['name_eng'];
3954                }
3955            } else {
3956                $textBookChapterName = $lessonOnair['Textbook']['name_eng'];
3957            }
3958        }
3959        
3960        $textBookChapterNameEng = '';
3961        if (isset($lessonOnair['Textbook']['name_eng']) && $lessonOnair['Textbook']['name_eng']) {
3962            $textBookChapterNameEng = $lessonOnair['Textbook']['name_eng'];
3963        } elseif ($userLocale != $engLanguage) {
3964            if (isset($glTextbook[$engLanguage]['gl_name']) && $glTextbook[$engLanguage]['gl_name']) {
3965                $textBookChapterNameEng = $glTextbook[$engLanguage]['gl_name'];
3966            }
3967        }
3968        
3969        $textbookClassName = $lessonOnair['TextbookSubCategory']['english_name'];
3970        if ($userLocale != Configure::read('default.user_language')) {
3971            if ($userLocale != $engLanguage) {
3972                if (isset($glTextbookSubcategory[$userLocale]['gl_name']) && $glTextbookSubcategory[$userLocale]['gl_name']) {
3973                    $textbookClassName = $glTextbookSubcategory[$userLocale]['gl_name'];
3974                } else {
3975                    $textbookClassName = $lessonOnair['TextbookSubCategory']['english_name'];
3976                }
3977            } else {
3978                $textbookClassName = $lessonOnair['TextbookSubCategory']['english_name'];
3979            }
3980        }
3981        
3982        $textbookClassNameEng = '';
3983        if (isset($lessonOnair['TextbookSubCategory']['english_name']) && $lessonOnair['TextbookSubCategory']['english_name']) {
3984            $textbookClassNameEng = $lessonOnair['TextbookSubCategory']['english_name'];
3985        } elseif ($userLocale != $engLanguage) {
3986            if (isset($glTextbookSubcategory[$engLanguage]['gl_name']) && $glTextbookSubcategory[$engLanguage]['gl_name']) {
3987                $textbookClassNameEng = $glTextbookSubcategory[$engLanguage]['gl_name'];
3988            }
3989        }
3990        
3991        $textbookCategoryName = $lessonOnair['TextbookCategory']['english_name'];
3992        if ($userLocale != Configure::read('default.user_language')) {
3993            if ($userLocale != $engLanguage) {
3994                if (isset($glTextbookCategory[$userLocale]['gl_name']) && $glTextbookCategory[$userLocale]['gl_name']) {
3995                    $textbookCategoryName = $glTextbookCategory[$userLocale]['gl_name'];
3996                } else {
3997                    $textbookCategoryName = $lessonOnair['TextbookCategory']['english_name'];
3998                }
3999            } else {
4000                $textbookCategoryName = $lessonOnair['TextbookCategory']['english_name'];
4001            }
4002        }
4003        
4004        $textbookCategoryNameEng = '';
4005        if (isset($lessonOnair['TextbookCategory']['english_name']) && $lessonOnair['TextbookCategory']['english_name']) {
4006            $textbookCategoryNameEng = $lessonOnair['TextbookCategory']['english_name'];
4007        } elseif ($userLocale != $engLanguage) {
4008            if (isset($glTextbookCategory[$engLanguage]['gl_name']) && $glTextbookCategory[$engLanguage]['gl_name']) {
4009                $textbookCategoryNameEng = $glTextbookCategory[$engLanguage]['gl_name'];
4010            }
4011        }
4012        
4013        $lastLessonTextBookCatgName = isset($userLastLesson['TextbookCategory']['name']) ? $userLastLesson['TextbookCategory']['name'] : null;
4014        $lastLessonTextBookSubCtgName = isset($userLastLesson['TextbookSubCategory']['name']) ? $userLastLesson['TextbookSubCategory']['name'] : null;
4015        $lastLessonTextbookChapterName = isset($userLastLesson['Textbook']['name']) ? $userLastLesson['Textbook']['name'] : null;
4016
4017        // - NC-6756 HOT FIX
4018        if (isset($userLastLesson['GlobalTextbookCategory']['gl_name']) && $userLastLesson['GlobalTextbookCategory']['gl_name']) {
4019            $lastLessonTextBookCatgName = $userLastLesson['GlobalTextbookCategory']['gl_name'];
4020        } elseif (isset($userLastLesson['TextbookCategory']['english_name']) && $userLastLesson['TextbookCategory']['english_name']) {
4021            $lastLessonTextBookCatgName = $userLastLesson['TextbookCategory']['english_name'];
4022        }
4023        if (isset($userLastLesson['GlobalTextbookSubCategory']['gl_name']) && $userLastLesson['GlobalTextbookSubCategory']['gl_name']) {
4024            $lastLessonTextBookSubCtgName = $userLastLesson['GlobalTextbookSubCategory']['gl_name'];
4025        } elseif (isset($userLastLesson['TextbookSubCategory']['english_name']) && $userLastLesson['TextbookSubCategory']['english_name']) {
4026            $lastLessonTextBookSubCtgName = $userLastLesson['TextbookSubCategory']['english_name'];
4027        }
4028        if (isset($userLastLesson['GlobalTextbook']['gl_name']) && $userLastLesson['GlobalTextbook']['gl_name']) {
4029            $lastLessonTextbookChapterName = $userLastLesson['GlobalTextbook']['gl_name'];
4030        } elseif (isset($userLastLesson['Textbook']['name_eng']) && $userLastLesson['Textbook']['name_eng']) {
4031            $lastLessonTextbookChapterName = $userLastLesson['Textbook']['name_eng'];
4032        }
4033
4034        // Current lesson or todays lesson
4035        if (isset($lessonOnair['GlobalTextbookCategory']['gl_name']) && $lessonOnair['GlobalTextbookCategory']['gl_name']) {
4036            $textbookCategoryName = $lessonOnair['GlobalTextbookCategory']['gl_name'];
4037        } elseif (isset($lessonOnair['TextbookCategory']['english_name']) && $lessonOnair['TextbookCategory']['english_name']) {
4038            $textbookCategoryName = $lessonOnair['TextbookCategory']['english_name'];
4039        }
4040        if (isset($lessonOnair['GlobalTextbookSubCategory']['gl_name']) && $lessonOnair['GlobalTextbookSubCategory']['gl_name']) {
4041            $textbookClassName = $lessonOnair['GlobalTextbookSubCategory']['gl_name'];
4042        } elseif (isset($lessonOnair['TextbookSubCategory']['english_name']) && $lessonOnair['TextbookSubCategory']['english_name']) {
4043            $textbookClassName = $lessonOnair['TextbookSubCategory']['english_name'];
4044        }
4045        if (isset($lessonOnair['GlobalTextbook']['gl_name']) && $lessonOnair['GlobalTextbook']['gl_name']) {
4046            $textBookChapterName = $lessonOnair['GlobalTextbook']['gl_name'];
4047        } elseif (isset($lessonOnair['Textbook']['name_eng']) && $lessonOnair['Textbook']['name_eng']) {
4048            $textBookChapterName = $lessonOnair['Textbook']['name_eng'];
4049        }
4050
4051        // check for main topic
4052        if ( isset($userLastLesson['Textbook']['main_topic_id']) && $userLastLesson['Textbook']['main_topic_id'] ) {
4053            $lastTextbookMainTopicName = ClassRegistry::init('Textbook')->getTextbookMainTopicName($userLastLesson['Textbook']['id'],Configure::read('default.teacher_timezone_id'));
4054            if ($lastTextbookMainTopicName) { 
4055                $lastLessonTextBookSubCtgName = $lastTextbookMainTopicName; 
4056            }
4057        }
4058        
4059        // check for main topic
4060        if ( isset($lessonOnair['Textbook']['main_topic_id']) && $lessonOnair['Textbook']['main_topic_id'] ) {
4061            $langId = ClassRegistry::init('CountryCode')->getUserLanguageId($userLocale);
4062            $textbookMainTopicName = ClassRegistry::init('Textbook')->getTextbookMainTopicName($lessonOnair['Textbook']['id'],$langId);
4063            $textbookMainTopicNameEn = ClassRegistry::init('Textbook')->getTextbookMainTopicName($lessonOnair['Textbook']['id'],Configure::read('default.teacher_timezone_id'));
4064            if ($textbookMainTopicName) { $textbookClassName = $textbookMainTopicName; }
4065            if ($textbookMainTopicNameEn) { $textbookClassNameEng = $textbookMainTopicNameEn; }
4066        }
4067
4068        $ongoinTextbookCategoryType = isset($lessonOnair['TextbookCategory']['textbook_category_type']) ? $lessonOnair['TextbookCategory']['textbook_category_type'] : null;
4069        $ongoinTextbookConnectId = isset($lessonOnair['LessonOnair']['connect_id']) ? $lessonOnair['LessonOnair']['connect_id'] : null;
4070
4071        $viewVars['last_lesson_date'] = isset($userLastLesson['LessonOnairsLog']['end_time']) ? date('m/d/Y', strtotime($userLastLesson['LessonOnairsLog']['end_time'])): 'None';
4072        $viewVars['last_lesson_textbook'] = ($lastLessonTextBookSubCtgName || $lastLessonTextbookChapterName) ? $lastLessonTextBookSubCtgName . ' - ' . $lastLessonTextbookChapterName : 'None';
4073        
4074        $viewVars['last_category_textbook'] = ($lastLessonTextBookCatgName) ? $lastLessonTextBookCatgName : 'None';
4075        $viewVars['last_subcategory_textbook'] = ($lastLessonTextBookSubCtgName ) ? $lastLessonTextBookSubCtgName : 'None';
4076        $viewVars['last_textbook'] = ($lastLessonTextbookChapterName) ? $orderLastLesson . $lastLessonTextbookChapterName : 'None';
4077
4078        $viewVars['category_textbook'] = $textbookCategoryName;
4079        $viewVars['subcategory_textbook'] = $textbookClassName;
4080        $viewVars['textbook'] = $order . $textBookChapterName;
4081
4082        $viewVars['lessonRequestTime'] = isset($lessonOnair['LessonOnair']['requested_lesson_time']) ? (floor($lessonOnair['LessonOnair']['requested_lesson_time']/60)) : 25;
4083        $viewVars['lessonRequestSelfIntroduction'] = isset($lessonOnair['LessonRequest']['self_introduction']) ? $lessonOnair['LessonRequest']['self_introduction'] : 1;
4084        $viewVars['lessonRequestIncorrectGrammar'] = isset($lessonOnair['LessonRequest']['incorrect_grammar']) ? $lessonOnair['LessonRequest']['incorrect_grammar'] : 1;
4085        $viewVars['lessonRequestOther'] = isset($lessonOnair['LessonRequest']['other_request']) ? $lessonOnair['LessonRequest']['other_request'] : 'none';
4086        $viewVars['textBookName'] = ($textBookChapterName || $textbookClassName) ? $textbookClassName . ' - '. $order . $textBookChapterName : null;
4087        $viewVars['textBookNameEng'] = ($textBookChapterNameEng || $textbookClassNameEng) ? $textbookClassNameEng .' - '. $textBookChapterNameEng: null;
4088        // check finish lesson
4089        $viewVars['lessonFinish'] = $lessonOnair['LessonRequest']['lesson_finish'];
4090
4091        // set false when textbook is callang or counseling
4092        $viewVars['lessonRequestinfoVisible'] = true;
4093        if (!$ongoinTextbookConnectId || in_array($ongoinTextbookCategoryType, Configure::read('isCallan')) || ($ongoinTextbookConnectId == Configure::read('counselor.connect_id'))) {
4094            $viewVars['lessonRequestinfoVisible'] = false;
4095        }
4096        // set the lesson type
4097        $viewVars['lessonType'] = $lessonOnair['LessonOnair']['lesson_type'];
4098
4099        // set the student Id
4100        $viewVars['studentId'] = $lessonOnair['LessonOnair']['user_id'];
4101        $viewVars['studentLessonLocalizeDir'] = $userLocale;
4102
4103        //NJ-60220
4104        $userLang = $this->User->getNativeLanguage($lessonOnair['LessonOnair']['user_id'] ?? 0);
4105        $viewVars['user_lang'] = $userLang;
4106
4107        // view vars
4108        $viewVars['studentUA'] = $lessonOnair['LessonOnair']['user_agent'];
4109
4110        // count student lesson
4111        $viewVars['studentLessonCount'] = isset($userLastLesson[0]['count_lesson']) ? $userLastLesson[0]['count_lesson'] : 0;
4112
4113        //get device type
4114        $viewVars['studentDeviceType'] = $studentDeviceType;
4115
4116        //get chocotto flg
4117        $viewVars['chocotto_camp_lesson_flg'] = $lessonOnair['LessonOnair']['chocotto_camp_lesson_flg'];
4118
4119        if ($lessonOnair['LessonOnair']['lesson_type'] == Configure::read('lesson.type.reservation') && isset($lessonOnair['LessonOnair']['user_id'])) {
4120            $viewVars['reserveTextbook'] = array(
4121                'category_id' => $lessonOnair['TextbookCategory']['id'],
4122                'subcategory_id' => $lessonOnair['TextbookSubCategory']['id'],
4123                'connect_id' => $ongoinTextbookConnectId,
4124                'chapter_id' => $lessonOnair['Textbook']['chapter_id'],
4125                'textbook_category_type' => $ongoinTextbookCategoryType,
4126                'is_callan' => in_array($ongoinTextbookCategoryType, Configure::read('isCallan')),
4127                'callan_progress' => ( in_array($ongoinTextbookCategoryType, Configure::read('callan_textbook_type')) ) ? $this->LessonOnairsLog->callanProgressMessageArr( array( "user_id" => $lessonOnair['LessonOnair']['user_id'], "textbook_category_type" => $ongoinTextbookCategoryType ) ) : array(),
4128                'audio_acquisition_flg' => $lessonOnair['TextbookCategory']['audio_acquisition_flg']
4129            );
4130        }
4131         
4132        $this->User->virtualFields['teacher_camera_toggle_flg'] = "(SELECT teachers.camera_toggle_flg FROM teachers WHERE teachers.id = ". $this->Auth->user('id') ." LIMIT 1)";
4133        // get the user's information
4134        $student = $this->User->find('first', array(
4135            'fields' => array(
4136                'User.nickname',
4137                'User.image_url',
4138                'User.id',
4139                'User.studysapuri_id',
4140                'User.callan_level_check',
4141                'User.nationality_id',
4142                'User.nationality_show_flg',
4143                'User.profile_image',
4144                'User.camera_toggle_flg',
4145                'User.corporate_id',
4146                'teacher_camera_toggle_flg'
4147            ),
4148            'conditions' => array(
4149                'User.id' => $lessonOnair['LessonOnair']['user_id'],
4150            ),
4151            'recursive' => -1
4152        ));
4153
4154        // check if student exists
4155        if ($student) {
4156            // get student information
4157            $studentInformation = new UserTable($student['User']);
4158
4159            // set student information
4160            $viewVars['studentImage'] = $studentInformation->getImageUrl();
4161            $viewVars['studentName'] = $studentInformation->nickname;
4162            $viewVars['callan_level_check'] = $studentInformation->callan_level_check;
4163            $viewVars['studentStudySapuriId'] = $studentInformation->studysapuri_id;
4164
4165            $corporateIdsDisabledFileUpload = Configure::read('corporate_ids_disabled_file_upload');
4166            $inCorporateTextbookControlFlg = in_array($studentInformation->corporate_id, $corporateIdsDisabledFileUpload) ? 1 : 0;
4167            $viewVars['corporateIdsDisabledFileUpload'] = $inCorporateTextbookControlFlg;
4168
4169            $nationalityLists = $this->Nationality->nationalityOptions();
4170            $studentNationality = (isset($nationalityLists[$studentInformation->nationality_id]) && $nationalityLists[$studentInformation->nationality_id]) ? $nationalityLists[$studentInformation->nationality_id] : '';
4171            $viewVars['studentNationality'] = $this->Nationality->getFlagNameAndImg(array('nationality_name' => $studentNationality, 'nationality_show' => $studentInformation->nationality_show_flg));
4172
4173            // if chocotto
4174            if($isChocottoUser = $this->isUserChocottoCamp($studentInformation->getMembershipTypeIndex())) {
4175                $usersDetail = $this->UsersDetail->chocottoCampUserDetails($studentInformation->id);
4176                $viewVars['studentCameraToggleFlag'] = (isset($usersDetail) && !empty($usersDetail['chocotto_camp_camera_flg']) && $usersDetail['chocotto_camp_camera_flg']) ? 1 : 0;
4177            }
4178
4179        // else set default image
4180        } else {
4181            $viewVars['studentImage'] = '/user/img/no_profile_img.jpg';
4182        }
4183
4184        // lesson camera toggle settings
4185        if(!isset($isChocottoUser)) {
4186            $viewVars['studentCameraToggleFlag'] = !empty($student['User']['camera_toggle_flg']) ? 1 : 0;
4187        }
4188        $viewVars['teacherCameraToggleFlag'] = !empty($student['User']['teacher_camera_toggle_flg']) ? 1 : 0;
4189
4190        // Update Current lesson's camera toggle settings
4191        $this->LessonOnair->updateAll(
4192            array(
4193                "LessonOnair.student_camera_toggle_flg" => $viewVars['studentCameraToggleFlag'],
4194                "LessonOnair.teacher_camera_toggle_flg" => $viewVars['teacherCameraToggleFlag']
4195            ),
4196            array(
4197                "LessonOnair.chat_hash" => $chatHash,
4198                "LessonOnair.student_camera_toggle_flg IS NULL",
4199                "LessonOnair.teacher_camera_toggle_flg IS NULL"
4200            )
4201        );
4202
4203        // return json vars
4204        return json_encode(array('error' => false, 'content' => $viewVars));
4205    }
4206
4207    /**
4208     * @api {post} /teacher/api/saveLessonConnectionType saveLessonConnectionType()
4209     * @apiName saveLessonConnectionType
4210     * @apiGroup API
4211     * @apiDescription Save lesson connection type
4212     * @apiSampleRequest off
4213     * 
4214     * @apiBody {Object} config Configuration data.
4215     * @apiBody {Object} connectionStats Connection statistics.
4216     * 
4217     * @apiSuccess {Boolean} error The error status.
4218     * @apiSuccess {String} content The content.
4219     *
4220     * @apiErrorExample {json} Success-Response:
4221     *     {
4222     *       "error": false,
4223     *       "content": "saved"
4224     *     }
4225     *
4226     * @apiErrorExample {json} Error-Response:
4227     *     {
4228     *       "error": true,
4229     *       "content": "invalid_request"
4230     *     }
4231     * 
4232      * @apiErrorExample {js} Used in: AngularJS
4233      * Location: "webroot/js/recruitment/webrtcv2/connect.js    
4234     */
4235    /**
4236     * save lesson connection type
4237     * -> this will be called once a stream between a student and teacher are established
4238     */
4239    public function saveLessonConnectionType () {
4240        $this->autoRender = false;
4241
4242        //MARK: - disallow saving of bitrate
4243        //September 28, 2017
4244        die();
4245
4246        // only allow ajax requests
4247        if (!$this->request->is('ajax')) {
4248            return json_encode(array('error' => true, 'content' => 'invalid_request'));
4249        }
4250
4251        // save bitrate only for office teachers
4252        if (
4253            !$this->Auth->user() ||
4254            ($this->Auth->user() && $this->Auth->user('home_flg') == 1)
4255        ) {
4256            // show info
4257            return json_encode(array('error' => true, 'content' => 'invalid_teacher'));
4258        }
4259
4260        // set post data
4261        $post = $this->request->data;
4262
4263        // set connection types
4264        $connectionType = 0;
4265        $config = $post['config'];
4266        $connectionStats = $post['connectionStats'];
4267
4268        // load model
4269        $this->loadModel("LessonOnairsConnectionStat");
4270
4271        // save lessononairs conncetion stat information
4272        $this->LessonOnairsConnectionStat->saveLessonConnectionStats(array(
4273            'teacher_id' => $config['teacherID'],
4274            'workstation_id' => $config['workstationID'],
4275            'chat_hash' => $config['chatHash'],
4276            'remote_candidate_type' => json_encode($connectionStats['remote_candidate_type']),
4277            'local_candidate_type' => json_encode($connectionStats['local_candidate_type']),
4278            'bytes_sent' => $connectionStats['bytes_sent'],
4279            'bytes_sent_diff' => $connectionStats['bytes_sent_diff'],
4280            'bytes_received' => $connectionStats['bytes_received'],
4281            'bytes_received_diff' => $connectionStats['bytes_received_diff'],
4282            'actual_bytes_sent' => $connectionStats['actual_bytes_sent'],
4283        ));
4284
4285        // show info
4286        return json_encode(array('error' => false, 'content' => 'saved'));
4287    }
4288
4289    /**
4290     * @api {post} /teacher/api/loadPreviousChatMessages loadPreviousChatMessages()
4291     * @apiName loadPreviousChatMessages
4292     * @apiGroup API
4293     * @apiDescription Load previous chat messages
4294     * @apiSampleRequest off
4295     * 
4296     * @apiBody {String} chatHash The chat hash.
4297     *
4298     * @apiSuccess {Boolean} error The error status.
4299     * @apiSuccess {String} content The content.
4300     * 
4301     * @apiErrorExample {json} Success-Response:
4302     *     {
4303     *       "error": false,
4304     *       "content": [...]
4305     *     }
4306     *
4307     * @apiErrorExample {json} Error-Response:
4308     *     {
4309     *       "error": true,
4310     *       "content": "invalid_request"
4311     *     }
4312     * 
4313     * @apiErrorExample {js} Used in: WebRTC
4314      * Location: "webroot/js/webrtcv2/event.common.js"
4315      * Location: "webroot/js/recruitment/webrtcv2/event.common.js"
4316     */
4317    /**
4318     * load previous chat messages
4319     */
4320    public function loadPreviousChatMessages () {
4321        $this->autoRender = false;
4322
4323        // only allow ajax requests
4324        if (!$this->request->is('ajax')) {
4325            return json_encode(array('error' => true, 'content' => 'invalid_request'));
4326        }
4327
4328        // set post data
4329        $post = $this->request->data;
4330
4331        // check if has chat hash
4332        if (!isset($post['chatHash'])) {
4333            return json_encode(array('error' => true, 'content' => 'invalid_chat_hash'));
4334        }
4335
4336        // load the previous messages
4337        $this->ChatHistory->openDBReplica();
4338        $lessonMessages = $this->ChatHistory->loadPreviousChatMessages($post['chatHash']);
4339        $this->ChatHistory->closeDBReplica();
4340
4341        // if has no lesson messages
4342        if (!$lessonMessages) {
4343            return json_encode(array('error' => false, 'content' => 'no_chat_messages'));
4344        }
4345
4346        // return data
4347        return json_encode(array('error' => false, 'content' => $lessonMessages));
4348    }
4349
4350    /**
4351     * @api {get} /teacher/api/checkSlotHasReservation checkSlotHasReservation()
4352     * @apiName checkSlotHasReservation
4353     * @apiGroup API
4354     * @apiDescription Check if slot has reservation
4355     * @apiSampleRequest off
4356     * 
4357     * @apiBody {String} teacherId The ID of the teacher.
4358     * @apiBody {String} reservedTime The reserved time to check.
4359     *
4360     * @apiSuccess {Boolean} isReserved The reservation status.
4361     * 
4362     * @apiErrorExample {json} Success-Response:
4363     *     {
4364     *       "isReserved": 1
4365     *     }
4366     *
4367     * @apiErrorExample {json} Error-Response:
4368     *     {
4369     *       "isReserved": 0
4370     *     }
4371     * 
4372      * @apiErrorExample {js} Used in: AngularJS
4373     * Location: "webroot/js/ng/controller/schedule.js"
4374     */
4375    /*
4376    * check if slot has reservation
4377    * @param  array $data GET data from ajax
4378    * @return json data
4379    */
4380    public function checkSlotHasReservation() {
4381        $this->autoRender = false;
4382        $this->layout = false;
4383        $isReserved = 0;
4384        $get = $this->params->query;
4385
4386        if (isset($get['teacherId']) && $get['teacherId'] && isset($get['reservedTime']) && $get['reservedTime']) {
4387            $teacherId = $get['teacherId'];
4388            $reservedTime = date('Y-m-d H:i:s', strtotime($get['reservedTime']));
4389            $isReserved = $this->LessonSchedule->isReservedFromTeacherIdAndReserveTime($teacherId, $reservedTime, null);
4390        }
4391
4392        return json_encode($isReserved);
4393    }
4394
4395    /**
4396     * @api {get} /teacher/api/reviveSessionChecker reviveSessionChecker()
4397     * @apiName reviveSessionChecker
4398     * @apiGroup API
4399     * @apiDescription Revive session if dead
4400     * @apiSampleRequest off
4401     * 
4402     * @apiBody {String} teacherID The ID of the teacher.
4403     * @apiBody {String} workstationID The ID of the workstation.
4404     * @apiBody {String} workstationName The name of the workstation.
4405     * @apiBody {String} status The status of the teacher.
4406     * @apiBody {String} remarks The remarks for the status.
4407     *
4408     * @apiSuccess {Boolean} error The error status.
4409     * @apiSuccess {String} content The content.
4410     * 
4411     * @apiErrorExample {json} Success-Response:
4412     *     {
4413     *       "error": false,
4414     *       "content": "session_recovered"
4415     *     }
4416     *
4417     * @apiErrorExample {json} Error-Response:
4418     *     {
4419     *       "error": true,
4420     *       "content": "Error message"
4421     *     }
4422     * 
4423      * @apiErrorExample {js} Used in: AngularJS
4424      * Location: "webroot/js/recruitment/webrtcv2/event.common.js"
4425      * Location: "webroot/js/webrtcv2/event.common.js"
4426     */
4427    /**
4428     * revive session if dead
4429     */
4430    public function reviveSessionChecker () {
4431        $this->autoRender = false;
4432        $this->autoLayout = false;
4433        
4434        // get $_GET data
4435        $getData = $this->request->query;
4436
4437        // check if teacherID params exists
4438        if (
4439            !isset($getData['teacherID']) ||
4440            !isset($getData['workstationID']) ||
4441            !isset($getData['workstationName']) ||
4442            !isset($getData['status']) ||
4443            !isset($getData['remarks'])
4444        ) {
4445            return json_encode(array('error' => true, 'content' => 'empty_teacher_id'));
4446        }
4447        
4448        // check if session still exists
4449        if ($this->Auth->user('id')) {
4450            // get last teacher status
4451            $this->TeacherStatus->openDBReplica();
4452            $teacherStatus = $this->TeacherStatus->find('first', array(
4453                'conditions' => array(
4454                    'TeacherStatus.teacher_id' => $getData['teacherID']
4455                ),
4456                'recursive' => -1
4457            ));
4458            $this->TeacherStatus->closeDBReplica();
4459            
4460            $this->LessonOnair->openDBReplica();
4461            $isDuringLesson = $this->LessonOnair->find('first', array(
4462                'conditions' => array(
4463                    'LessonOnair.teacher_id' => $getData['teacherID'],
4464                    'LessonOnair.status' => array(2, 3)
4465                ),
4466                'recursive' => -1
4467            ));
4468            $this->LessonOnair->closeDBReplica();
4469
4470            if (
4471                    $teacherStatus &&
4472                    isset($teacherStatus['TeacherStatus']) &&
4473                    $teacherStatus['TeacherStatus']['status'] == 6
4474                ) {    
4475                // - if loggedout for an hour
4476                if ($teacherStatus['TeacherStatus']['remarks2'] == "admin_force_teacher_logout_forceLogout") {
4477                    return json_encode(array('error' => true, 'content' => 'admin_force_teacher_logged_out'));                
4478                // - default -> do nothing
4479                } else {
4480                    return json_encode(array('error' => true, 'content' => 'teacher_logged_out', 'is_during_lesson' => $isDuringLesson ? 1 : 0));
4481                }
4482            } else {
4483                // retrieve teacher status
4484                return json_encode(array('error' => true, 'content' => 'session_exists'));
4485            }
4486        }
4487
4488        // get last teacher status
4489        $this->TeacherStatus->openDBReplica();
4490        $teacherStatus = $this->TeacherStatus->find('first', array(
4491            'conditions' => array(
4492                'TeacherStatus.teacher_id' => $getData['teacherID']
4493            ),
4494            'recursive' => -1
4495        ));
4496        $this->TeacherStatus->closeDBReplica();
4497
4498        // *if Workstation ID is empty recover worktation from current status
4499        if ($teacherStatus && empty($getData['workstationID'])) {
4500            $this->log(__METHOD__ . ': '. date('Y-m-d H:i:s'). ' Teacher: '.$getData['teacherID'].' empty workstation', 'debug');
4501            $workstationId = $teacherStatus['TeacherStatus']['workstation_id'];
4502
4503            $con = array(
4504                    'fields' => array('Workstation.name'),
4505                    'conditions' => array('Workstation.id' => $workstationId),
4506                    'recursive' => -1
4507                  );
4508
4509            $getStatus = $this->Workstation->find('first', $con);
4510            if ($getStatus) {
4511                $getData['workstationID'] = $workstationId;
4512                $getData['workstationName'] = $getStatus['Workstation']['name'];
4513            }
4514        }
4515
4516        // check if last status is logged out
4517        if (
4518            $this->Auth->user('id') &&
4519            $teacherStatus &&
4520            isset($teacherStatus['TeacherStatus']) &&
4521            $teacherStatus['TeacherStatus']['status'] == 6
4522        ) {    
4523            // - if loggedout for an hour
4524            if ($teacherStatus['TeacherStatus']['remarks2'] == Configure::read('remarks2.1hour_not_standby')) {
4525                $this->Auth->logout();
4526                return json_encode(array('error' => true, 'content' => Configure::read('remarks2.1hour_not_standby')));            
4527            }else {
4528                return json_encode(array('error' => true, 'content' => 'teacher_logged_out'));
4529            }
4530        }
4531        else{
4532            return json_encode(array('error' => true, 'content' => 'session_exists'));
4533        }
4534
4535        // get teacher information
4536        $this->Teacher->openDBReplica();
4537        $teacherInfo = $this->Teacher->find('first', array(
4538            'conditions' => array(
4539                'Teacher.id' => $getData['teacherID'],
4540                'Teacher.status' => 1
4541            ),
4542            'recursive' => -1
4543        ));
4544        $this->Teacher->closeDBReplica();
4545
4546        // check if teacher information exists
4547        if (!$teacherInfo) {
4548            return json_encode(array('error' => true, 'content' => 'empty_teacher'));
4549        }
4550
4551        // set teacher info
4552        $teacherInfo = $teacherInfo['Teacher'];
4553
4554        // set teacher type
4555        $teacherInfo['type'] = 'teacher';
4556        $this->Auth->login($teacherInfo);
4557
4558        // set teacher status
4559        $data = array(
4560            'teacher_id' => $getData['teacherID'],
4561            'workstation_id' => $getData['workstationID'],
4562            'workstation_name' => $getData['workstationName'],
4563            'status' => $getData['status'],
4564            'remarks1' => $getData['remarks'],
4565            'remarks2' => "session_recovery"
4566        );
4567        $nowTeacherStatus = $getData['status'];
4568
4569        // check if last teacher status exists
4570        if (!$teacherStatus) {
4571            // check if others or mealbreak
4572            if ($nowTeacherStatus == 6 || $nowTeacherStatus == 7) {
4573                $data['status'] = 4;
4574            }
4575
4576            if(isset($data['status']) && $data['status'] == 2) {
4577                $this->log(__METHOD__ . '[TEACHER STATUS STANDBY] teacher_id -> ' . json_encode($getData['teacherID']), 'error');
4578            }
4579
4580            // save teacher status
4581            TeacherStatusLogTable::save($data);
4582            TeacherStatusTable::save($data);
4583        }
4584
4585        // set teacher session
4586        myTools::init_session(true);
4587        $this->Session->write('Teacher.status', $nowTeacherStatus);
4588        $this->Session->write('Teacher.remarks', $data['remarks1']);
4589        $this->Session->write('Teacher.remarks', $getData['remarks2']);
4590        $this->Session->write('Teacher.workstationId', $getData['workstationID']);
4591        $this->Session->write('Teacher.workstationName', $getData['workstationName']);
4592
4593        myTools::init_session();
4594
4595        //assign memcache as backup of workstation
4596        $memcached = new myMemcached();
4597        $memcached->set(array('key' => 'Teacher.workstationId.'.$getData['teacherID'], 'value' => $getData['workstationID']));
4598        $memcached->set(array('key' => 'Teacher.workstationName.'.$getData['teacherID'], 'value' => $getData['workstationName']));
4599
4600        // session was recovered
4601        return json_encode(array('error' => false, 'content' => 'session_recovered'));
4602    }
4603
4604    /**
4605     * @api {post} /teacher/api/syncScheduleFromAccounting syncScheduleFromAccounting()
4606     * @apiName syncScheduleFromAccounting
4607     * @apiGroup API
4608     * @apiDescription used to sync teacher schedules.
4609     * @apiSampleRequest off
4610     * 
4611     * @apiSuccess {Boolean} result The result of the sync.
4612     * 
4613     * @apiErrorExample {json} Success-Response:
4614     *     {
4615     *       "result": true
4616     *     }
4617     *
4618     * @apiErrorExample {json} Error-Response:
4619     *     {
4620     *       "result": false
4621     *     }
4622     * 
4623      * @apiErrorExample {js} Used in: AngularJS
4624     * Location: "webroot/js/ng/controller/schedule.js"
4625     */
4626    /**
4627    * Call the function that sync teacher schedules
4628    */
4629    public function syncScheduleFromAccounting() {
4630        $this->autoRender = false;
4631        $result = false;
4632        if ($this->request->is('post') && !$this->homeBase) {
4633            $result = $this->ShiftWorkOn->syncShiftFromAccounting($this->Auth->user('id'));
4634        }
4635        return json_encode(array('result' => $result));
4636    }
4637
4638    /**
4639     * @api {get} /teacher/api/getUnlessonReservationAndLessonFinishTime getUnlessonReservationAndLessonFinishTime()
4640     * @apiName getUnlessonReservationAndLessonFinishTime
4641     * @apiGroup API
4642     * @apiDescription used to get the unlesson reservation and lesson finish time.
4643     * @apiSampleRequest off
4644     * 
4645     * @apiSuccess {Boolean} reservation The reservation status.
4646     * @apiSuccess {Boolean} tosUser The TOS user status.
4647     * @apiSuccess {Boolean} finishUnlessonReservation The finish unlesson reservation status.
4648     * @apiSuccess {Integer} reservationId The reservation ID.
4649     * @apiSuccess {Integer} timeoutDuration The timeout duration.
4650     * 
4651     * @apiErrorExample {json} Success-Response:
4652     *     {
4653     *       "reservation": true,
4654     *       "tosUser": true,
4655     *       "finishUnlessonReservation": true,
4656     *       "reservationId": 123,
4657     *       "timeoutDuration": 1500000
4658     *     }
4659     *
4660     * @apiErrorExample {json} Error-Response:
4661     *     {
4662     *       "reservation": false,
4663     *       "tosUser": false,
4664     *       "finishUnlessonReservation": false
4665     *     }
4666     * 
4667      * @apiErrorExample {js} Used in: AngularJS
4668      * Location: "webroot/js/ng/app.js"
4669     */
4670    public function getUnlessonReservationAndLessonFinishTime() {
4671        $this->autoRender = false;
4672        $teacherId = $this->Auth->user('id');
4673        $result = [
4674            'reservation' => false,
4675            'tosUser' => false,
4676            'finishUnlessonReservation' => false
4677        ];
4678
4679        // check if booked
4680        $padding = Configure::read('reservation_padding');
4681        $lessonTime = 25;
4682        $lessonConfig = Configure::read('lesson');
4683        $status = $lessonConfig['status'];
4684        $procDate = date('Y-m-d H:i:s');
4685        $conditions = array(
4686            'LessonSchedule.teacher_id' => $teacherId,
4687            'LessonSchedule.status' => 1,
4688            'DATE_FORMAT(lesson_time + interval ' . $lessonTime . ' minute,"%Y-%m-%d %H:%i") >=' => date('Y-m-d H:i', strtotime($procDate)),
4689            'DATE_FORMAT(lesson_time - interval ' .$padding . ' minute,"%Y-%m-%d %H:%i") <=' => date('Y-m-d H:i', strtotime($procDate))
4690        );
4691
4692        // get unlesson reservation
4693        $this->LessonSchedule->openDBReplica();
4694        $data = $this->LessonSchedule->find('first', array(
4695            'fields' => array(
4696                'LessonSchedule.lesson_time',
4697                'LessonSchedule.id',
4698                'LessonSchedule.user_id'
4699            ),
4700            'conditions' => $conditions,
4701            'recursive' => -1
4702        ));
4703        $this->LessonSchedule->closeDBReplica();
4704
4705        if (!empty($data)) {
4706            $ls = $data['LessonSchedule'];
4707            $result['tosUser'] = $this->UsersExtend->checkIfTosUser($ls['user_id']);
4708
4709            if ($result['tosUser']) {
4710                $result['reservation'] = true;
4711                # add 25 minutes
4712                $finishTime = strtotime('+25 minutes '.$ls['lesson_time']);
4713                $currentTime = time();
4714
4715                # check if current date is greater than cancel time.
4716                if ($currentTime <= $finishTime) {
4717                    $result['finishUnlessonReservation'] = true;
4718                    $finishTime = $finishTime - $currentTime;
4719
4720                    $result['reservationId'] = $ls['id'];
4721                    $result['timeoutDuration'] =  $finishTime * 1000; # 1000 ms = 1 second
4722                }
4723            }
4724        }
4725
4726        return json_encode($result);
4727    }
4728
4729    /**
4730     * @api {post} /teacher/api/finishUnlessonReservation finishUnlessonReservation()
4731     * @apiName finishUnlessonReservation
4732     * @apiGroup API
4733     * @apiDescription used to finish an unlesson reservation.
4734     * @apiSampleRequest off
4735     * 
4736     * @apiBody {String} reservation_id The ID of the reservation.
4737     *
4738     * @apiSuccess {Boolean} lessonFinish The lesson finish status.
4739     * @apiSuccess {Boolean} canStanbyForReservation The standby for reservation status.
4740     * 
4741     * @apiErrorExample {json} Success-Response:
4742     *     {
4743     *       "lessonFinish": true,
4744     *       "canStanbyForReservation": true
4745     *     }
4746     *
4747      * @apiErrorExample {js} Used in: AngularJS
4748      * Location: "webroot/js/ng/app.js"
4749     */
4750    public function finishUnlessonReservation() {
4751        $this->autoRender = false;
4752        $ret = ['lessonFinish' => false];
4753
4754        if ($this->request->is('post')) {
4755            $post = $this->request->data;
4756            $lsId = $post['reservation_id'];
4757
4758            $this->LessonSchedule->virtualFields = ['onair_id' => "SELECT lo.id FROM lesson_onairs lo where lo.teacher_id = LessonSchedule.teacher_id and lo.status = 2 LIMIT 1"];
4759            $data = $this->LessonSchedule->find('first', [
4760                'fields' => [
4761                    'LessonSchedule.id',
4762                    'LessonSchedule.user_id',
4763                    'LessonSchedule.teacher_id',
4764                    'LessonSchedule.connect_id',
4765                    'LessonSchedule.payment_plan_id',
4766                    'LessonSchedule.lesson_time',
4767                    'LessonSchedule.onair_id'
4768                ],
4769                'joins' => [
4770                    [
4771                        'table' => 'users_extend',
4772                        'alias' => 'UsersExtend',
4773                        'type' => 'INNER',
4774                        'conditions' => 'UsersExtend.user_id = LessonSchedule.user_id'
4775                    ]
4776                ],
4777                'conditions' => ['LessonSchedule.id' => $lsId]
4778            ]);
4779
4780            if ($data) {
4781                $ls = $data['LessonSchedule'];
4782                $endTime = date('Y-m-d H:i:s', strtotime($ls['lesson_time']) + 60 * 26);
4783
4784                // set params
4785                $param = array(
4786                    'method' => APP_DIR.' | '.__METHOD__,
4787                    'action_by' => 1,
4788                    'student_lesson_localize_dir' => Configure::read('default.user_language'),
4789                    'tos_special_reserved_lesson_flg' => true,
4790                    'tos_special_reserved_lesson_data' => [
4791                        'status' => 3, // lesson
4792                        'user_id' => $ls['user_id'],
4793                        'payment_plan_id' => $ls['payment_plan_id'],
4794                        'lesson_type' => 2, // lesson
4795                        'connect_id' => $ls['connect_id'],
4796                        'lesson_schedule_id' => $ls['id'],
4797                        'start_time' => $ls['lesson_time'],
4798                        'end_time' => $endTime
4799                    ]
4800                );
4801
4802                if (LessonOnairTable::delete($ls['onair_id'], $param)) {
4803                    $ret['canStanbyForReservation'] = true;
4804                    $ret['lessonFinish'] = true;
4805
4806                    if ($this->homeBase) {
4807                        // get reservation data
4808                        $this->LessonSchedule->openDBReplica();
4809                        $nextReservationData = $this->LessonSchedule->find('first', array(
4810                            'fields' => array('lesson_time'),
4811                            'conditions' => array(
4812                                'teacher_id' => $lsId,
4813                                'lesson_time >' => $endTime
4814                            ),
4815                            'order' => 'lesson_time ASC',
4816                            'recursive' => -1
4817                        ));
4818
4819                        $this->LessonSchedule->closeDBReplica();
4820
4821                        if (!$nextReservationData) {
4822                            $ret['canStanbyForReservation'] = false;
4823                        }
4824                    }
4825                }
4826            }
4827        }
4828
4829        return json_encode($ret);
4830    }
4831
4832    /**
4833     * @api {get} /teacher/api/getReservationAndCancelTime getReservationAndCancelTime()
4834     * @apiName getReservationAndCancelTime
4835     * @apiGroup API
4836     * @apiDescription used to check if a lesson is reserved and get the time to show the special modal.
4837     * @apiSampleRequest off
4838     * 
4839     * @apiSuccess {Boolean} reservation The reservation status.
4840     * @apiSuccess {Integer} userId The user ID.
4841     * @apiSuccess {String} lesson_time The lesson time.
4842     * @apiSuccess {Boolean} tosUser The TOS user status.
4843     * @apiSuccess {Integer} specialTimeoutDuration The special timeout duration.
4844     * @apiSuccess {String} custom_lesson_time The custom lesson time.
4845     * 
4846     * 
4847     * @apiErrorExample {json} Success-Response:
4848     *     {
4849     *       "reservation": true,
4850     *       "userId": 123,
4851     *       "lesson_time": "2023-10-01 10:00:00",
4852     *       "tosUser": true,
4853     *       "specialTimeoutDuration": 300,
4854     *       "custom_lesson_time": "20231001100000"
4855     *     }
4856     *
4857     * @apiErrorExample {json} Error-Response:
4858     *     {
4859     *       "reservation": false,
4860     *       "tosUser": false,
4861     *       "specialTimeoutDuration": 0,
4862     *       "custom_lesson_time": ""
4863     *     }
4864     * 
4865      * @apiErrorExample {js} Used in: AngularJS
4866      * Location: "webroot/js/ng/controller/header.js"
4867      * Location: "webroot/js/ng/app.js"
4868     */
4869    /*
4870    * check if lesson reservation.
4871    * get the lesson_schedule id and cancelled time for the student will not come.
4872    * @return json data
4873    */
4874    public function getReservationAndCancelTime() {
4875        $this->autoRender = false;
4876        $teacherId = $this->Auth->user('id');
4877        $result = LessonOnairTable::getReservationAndCancelTime($teacherId);
4878
4879        $result['tosUser'] = $result['reservation'] ? $this->UsersExtend->checkIfTosUser($result['userId']) : false;
4880        $result['specialTimeoutDuration'] = ($result['reservation'] && $result['tosUser']) ? $this->getRLSShowModalTime($result) : 0;
4881        $result['custom_lesson_time'] = ($result['reservation'] && $result['tosUser']) ? date('YmdHis', strtotime($result['lesson_time'])) : '';
4882
4883        return json_encode($result);
4884    }
4885
4886    /**
4887     * Get the remaining time (show special modal if timeoutDuration is 0)
4888     * @param  array $params
4889     * @return int
4890     */
4891    private function getRLSShowModalTime($params) {
4892        $this->autoRender = false;
4893
4894        if (!isset($params['userId'])) {
4895            return false;
4896        }
4897
4898        if (!isset($params['lesson_time'])) {
4899            return false;
4900        }
4901
4902        // set variables
4903        $userId = $params['userId'];
4904        $lessonTime = $params['lesson_time'];
4905        $currentTime = time();
4906        $showModalTime = strtotime('+5 minutes ' . $lessonTime);
4907
4908        // check if current date is greater than or equal to show modal time.
4909        if ($currentTime >= $showModalTime) {
4910            $timeoutDuration = 0;
4911        } else {
4912            $timeoutDuration = $showModalTime - $currentTime;
4913            $timeoutDuration = $timeoutDuration * 1000; # 1000 ms = 1 second
4914        }
4915
4916        return $timeoutDuration;
4917    }
4918
4919    /**
4920     * @api {post} /teacher/api/cancelReservation cancelReservation()
4921     * @apiName cancelReservation
4922     * @apiGroup API
4923     * @apiDescription used to cancel a reservation.
4924     * @apiSampleRequest off
4925     * 
4926     * @apiBody {String} reservation_id The ID of the reservation.
4927     * @apiBody {String} lesson_time The time of the lesson.
4928     * @apiBody {String} [teacher_id] The ID of the teacher (optional).
4929     *
4930     * @apiSuccess {Boolean} success The success status.
4931     * @apiSuccess {Boolean} isCancelled The cancelled status.
4932     * 
4933     * @apiErrorExample {json} Success-Response:
4934     *     {
4935     *       "success": true
4936     *     }
4937     *
4938     * @apiErrorExample {json} Error-Response:
4939     *     {
4940     *       "success": false,
4941     *       "isCancelled": true
4942     *     }
4943     * 
4944      * @apiErrorExample {js} Used in: AngularJS
4945      * Location: "webroot/js/ng/app.js"
4946      * Location: "webroot/js/recruitment/ng/app.js"
4947     */
4948    /*
4949    * cancel reservation
4950    * @post data reservationId
4951    * @return json data
4952    */
4953    public function cancelReservation() {
4954        $this->autoRender = false;
4955        $result['success'] = false;
4956        if ($this->request->is('post')) {
4957            $data = $this->request->data;
4958            $reservationId = $this->request->data['reservation_id'];
4959            $lessonTime = $this->request->data['lesson_time'];
4960            $cancellationReason = isset($data['cancellation_reason']) ? trim($data['cancellation_reason']) : null;
4961
4962            $dateTime = $lessonTime.":00";
4963            $teacherId = isset($this->request->data['teacher_id']) ? $this->request->data['teacher_id'] : $this->Auth->user('id');
4964            $this->log(__METHOD__ . ': '. date('Y-m-d H:i:s'). ' -- perform late cancellation', 'debug');
4965
4966            // check if reservation was was already cancelled
4967            $reservationCancelled = $this->LessonScheduleCancel->find('first', array(
4968                'fields' => array('cancelled_date'),
4969                'conditions' => array(
4970                    'teacher_id' => $teacherId,
4971                    'lesson_time' => $lessonTime,
4972                    'reservation_id' => $reservationId
4973                ),
4974                'recursive' => -1
4975            ));
4976
4977            $this->log(__METHOD__.'line 3034 ' . json_encode([$reservationCancelled,$result]), 'debug');
4978
4979            if ($reservationCancelled) {
4980                $result['isCancelled'] = true;
4981                return json_encode($result);
4982            }
4983
4984            //NC-6512 check if teacher can cancel 5/10 minutes before the reservation time
4985            $teacherData = $this->Teacher->findById($teacherId);
4986            $cancelTime = strtotime('-5 minutes '.$dateTime);
4987            if (isset($teacherData['Teacher']['counseling_flg']) && $teacherData['Teacher']['counseling_flg']) {
4988                $cancelTime = strtotime('-10 minutes '.$dateTime);
4989            }
4990            
4991            $lateCancellation = strtotime('+6 minutes'.$dateTime);
4992
4993            $this->log(__METHOD__.'line 3043 ' . json_encode([$cancelTime,$lateCancellation,$result]), 'debug');
4994
4995            if ($cancelTime <= time() && $lateCancellation >= time()) {
4996                return json_encode($result);
4997            }
4998
4999            $isLateCancelation = time() >= $lateCancellation;
5000            $params = array(
5001                'reservationId' => $reservationId,
5002                'isTeacherCancelation' => true,
5003                'isLateCancelation' => $isLateCancelation,
5004                'cancelFrom' => 'teacher',
5005                'notHomeBased' => (!$this->homeBase) ? true : null,
5006                'cancellationReason' => $cancellationReason,
5007            );
5008            LessonScheduleTable::cancelReservationAndRefund($params);
5009
5010            $hasShiftWorkOn = $this->ShiftWorkOn->checkDataExist($teacherId, $lessonTime, true);
5011
5012            // set status to 0
5013            if ($hasShiftWorkOn) {
5014                $this->LessonScheduleCancel->openDBReplica();
5015                $cancelledSched = $this->LessonScheduleCancel->find('first', array(
5016                    'conditions' => array(
5017                        'reservation_id' => $reservationId,
5018                        'status' => Configure::read('lock_shift_cancellation_types')
5019                    ),
5020                    'recursive' => -1
5021                ));
5022                $this->LessonScheduleCancel->closeDBReplica();
5023
5024                $shiftWorkOnData = array('status' => 0, 'lock_flg' => 0);
5025                if ($cancelledSched) {
5026                    $shiftWorkOnData['lock_flg'] = 1;
5027                }
5028
5029                $this->ShiftWorkOn->read(array('status', 'lock_flg'), $hasShiftWorkOn['ShiftWorkOn']['id']);
5030                $this->ShiftWorkOn->set($shiftWorkOnData);
5031                $this->ShiftWorkOn->save();
5032            }
5033
5034            //-- lesson time push notification
5035            $lessonTimeNotifParams = array(
5036                'lesson_time'     => date('Y-m-d H:i:s', strtotime($lessonTime)),
5037                'teacher_id'     => $teacherId
5038            );
5039            ClassRegistry::init('FcmDeviceToken')->lessonSlotPushNotification($lessonTimeNotifParams);
5040            $result['success'] = true;
5041        }
5042        return json_encode($result);
5043    }
5044
5045    /**
5046     * @api {post} /teacher/api/getStudentDisconnectionTime getStudentDisconnectionTime()
5047     * @apiName getStudentDisconnectionTime
5048     * @apiGroup API
5049     * @apiDescription Get student's last disconnection time by chat_hash.
5050     * @apiSampleRequest off
5051     * 
5052     * @apiBody {String} chatHash The chat hash.
5053     * 
5054     * @apiSuccess {Integer} student_disconnection_time The student disconnection time.
5055     * @apiError {Boolean} error The error status.
5056     * @apiError {String} content The content.
5057     *
5058     * @apiErrorExample {json} Success-Response:
5059     *     {
5060     *       "student_disconnection_time": 30000
5061     *     }
5062     *
5063     * @apiErrorExample {json} Error-Response:
5064     *     {
5065     *       "error": true,
5066     *       "content": "invalid_request"
5067     *     }
5068     */
5069    /**
5070     * get student's last disconnection time by chat_hash
5071     */
5072    public function getStudentDisconnectionTime () {
5073        $this->autoRender = false;
5074
5075        // only allow ajax requests
5076        if (!$this->request->is('ajax')) {
5077            return json_encode(array('error' => true, 'content' => 'invalid_request'));
5078        }
5079
5080        // set post data
5081        $post = $this->request->data;
5082
5083        // check if chathash exists
5084        if (!isset($post['chatHash'])) {
5085            return json_encode(array('error' => true, 'content' => 'invalid_params'));
5086        }
5087
5088        // get vars
5089        $chatHash = $post['chatHash'];
5090
5091        // declare myMemcached
5092        $memcached = new myMemcached();
5093
5094        // get last student disconnection from memcached
5095        $studentDisconnectionTime = $memcached->get('DC_' . $chatHash);
5096
5097        // if has content
5098        if ($studentDisconnectionTime) {
5099            $studentDisconnectionTime = time() - $studentDisconnectionTime;
5100            $studentDisconnectionTime = Configure::read('lesson_heartbeat_config_v2.disconnection_timeout') - $studentDisconnectionTime;
5101            $studentDisconnectionTime = $studentDisconnectionTime <= 0 ? 0 : $studentDisconnectionTime;
5102
5103        // if has no memcached, use 60 seconds
5104        } else {
5105            $this->log("[STUDENT_DISCONNECTION] student has no disconnection time -> " . json_encode($post), "debug");
5106            return json_encode(array('error' => true, 'content' => 'no_memcached_disconnection_time'));
5107        }
5108
5109        // multiply by 1000
5110        $studentDisconnectionTime = $studentDisconnectionTime * 1000;
5111
5112        // return json encoded array
5113        return json_encode(array('student_disconnection_time' => $studentDisconnectionTime));
5114    }
5115
5116    /**
5117    * @api {get} /teacher/api/finishStatusChange finishStatusChange()
5118    * @apiName finishStatusChange
5119    * @apiGroup API
5120    * @apiSampleRequest off
5121    * @apiDescription used to denote that the teacher has finished changing status.
5122    *
5123    * @apiErrorExample {js} Used in: Elements
5124    * Location: "view/Elements/header.php"
5125    */
5126    /**
5127     * this will denote that the teacher has finished changing status
5128     */
5129    public function finishStatusChange () {
5130        $this->autoRender = false;
5131
5132        // check if has request data
5133        $getData = $this->request->data;
5134        $teacherId = $this->Auth->user('id');
5135
5136        // create random salt for debugging
5137        $randomSalt = substr((md5(uniqid(rand(),1))), 0, 6);
5138
5139        // debug
5140        $this->log(__METHOD__ . " LESTER | -> cleaning up status change! : " . $randomSalt . " | teacher_id : " . json_encode($getData), "debug");
5141
5142        // if has no authenticated user
5143        if (!$teacherId && isset($getData['teacherId'])) {
5144            $teacherId = $getData['teacherId'];
5145        }
5146
5147        // if has no teacher ID
5148        if (!$teacherId) {
5149            $this->log(__METHOD__ . " LESTER | -> unable to finish status change as session is dead! : " . $randomSalt . " | teacher_id : " . $teacherId, "debug");
5150            return;
5151        }
5152
5153        // log
5154        $this->log("[SHUTDOWN] deleting cookies -> " . json_encode($this->Cookie->read('TEACHER_STATUS_' . $teacherId)), "debug");
5155
5156        // delete cookie
5157        $this->Cookie->delete('TEACHER_STATUS_' . $teacherId);
5158        $this->Cookie->delete('TEACHER_THREE_MINUTES_BREAK_' . $teacherId);
5159
5160        // debug
5161        $this->log(__METHOD__ . " LESTER | -> cleaning up session, session: " . $this->Session->read('Teacher.threeMinOtherBreak') . "| cookie : " . json_encode($this->Cookie->read('TEACHER_STATUS_' . $teacherId)) . "| salt: " . $randomSalt . " | teacher_id : " . $teacherId, "debug");
5162    }
5163
5164    /**
5165     * @api {post} /teacher/api/saveImageLink saveImageLink()
5166     * @apiName saveImageLink
5167     * @apiGroup API
5168     * @apiDescription used to store the snap shot of the teacher, used in admin teacher monitor.
5169     * @apiSampleRequest off
5170     * 
5171     * @apiBody {String} image_url The URL of the image.
5172     * @apiBody {String} teacher_status The status of the teacher.
5173     *
5174     * @apiSuccess {String} result The result of the save.
5175     * 
5176     * @apiErrorExample {json} Success-Response:
5177     *     {
5178     *       "result": "saved"
5179     *     }
5180     */
5181    /**
5182    * this will store the snap shot of the teacher, used in admin teacher monitor
5183    */
5184    public function saveImageLink() {
5185        $this->autoRender = false;
5186        return false;
5187
5188        if (!$this->Auth->user('id')) {
5189            return false;
5190        }
5191
5192        $data = $this->request->data;
5193        $imageURL = isset($data['image_url']) ? $data['image_url'] : null;
5194        $teacherStatus = isset($data['teacher_status']) ? $data['teacher_status'] : null;
5195        $teacherId = $this->Auth->user('id');
5196
5197        if ($this->request->is('post')) {
5198            $this->TeacherMonitorImages->create();
5199            $this->TeacherMonitorImages->set(array(
5200                'teacher_id' => $teacherId,
5201                'image_link' => $imageURL,
5202                'teacher_status' => $teacherStatus,
5203            ));
5204            if ($this->TeacherMonitorImages->save()) {
5205                return json_encode(array('result' => 'saved'));
5206            }
5207        }
5208    }
5209
5210    /**
5211     * @api {post} /teacher/api/get3MinutesBreakRemainingTime get3MinutesBreakRemainingTime()
5212     * @apiName get3MinutesBreakRemainingTime
5213     * @apiGroup API
5214     * @apiDescription used to get the teacher 3 minutes break remaining time in seconds.
5215     * @apiSampleRequest off
5216     * 
5217     * @apiSuccess {Boolean} status The status of the request.
5218     * @apiSuccess {Integer} remainingTime The remaining time in seconds.
5219     * @apiSuccess {Boolean} shift_meal_break The shift meal break status.
5220     * @apiSuccess {String} avatar_phrase_html The avatar phrase HTML.
5221     * 
5222     * @apiErrorExample {json} Success-Response:
5223     *     {
5224     *       "status": true,
5225     *       "remainingTime": 180,
5226     *       "shift_meal_break": false,
5227     *       "avatar_phrase_html": "<tr>...</tr>"
5228     *     }
5229     *
5230     * @apiErrorExample {json} Error-Response:
5231     *     {
5232     *       "status": false,
5233     *       "error": "Error message"
5234     *     }
5235     * 
5236      * @apiErrorExample {js} Used in: AngularJS
5237      * Location: "webroot/js/ng/home.js"
5238      * Location: "webroot/js/ng/controller/home.js"
5239     */
5240    /**
5241     * get teacher 3 minutes break remaining time in seconds
5242     * return array json_encode $returnData
5243     */
5244    public function get3MinutesBreakRemainingTime() {
5245        $this->autoRender = false;
5246        $remainingTime = 0;
5247        $teacherId = $this->Auth->user('id');
5248
5249        // if not AJAX request
5250        if (!$this->request->is('ajax')) {
5251            $this->log("[3_MINUTES_BREAKTIME] request is not AJAX", "debug");
5252            throw new Exception("invalid_request_type");
5253        }
5254
5255        // check if teacherId is empty
5256        if (empty($teacherId)) {
5257            $this->log("[3_MINUTES_BREAKTIME] teacher id is empty", "debug");
5258            throw new Exception("teacher_id_empty");
5259        }
5260
5261        if ($this->request->is('ajax')) {
5262            // get 3 minutes break start time
5263            $threeMinOtherBreakStartTime = $this->Cookie->read('threeMinOtherBreakStartTime_'.$teacherId);
5264
5265            $threeMinOtherBreakCurrentTime = time();
5266            if (empty($threeMinOtherBreakStartTime)) {
5267
5268                $ts = $this->TeacherStatus->find('first', array(
5269                    'fields' => array(
5270                        'TeacherStatus.teacher_id',
5271                        'TeacherStatus.status',
5272                        'TeacherStatus.remarks1',
5273                        'TeacherStatus.remarks2',
5274                        'TeacherStatus.created'
5275                    ),
5276                    'conditions' => array(
5277                        'TeacherStatus.teacher_id' => (int)$teacherId
5278                    )
5279                ));
5280
5281                //get time
5282                if (date('i') >= 30) {
5283                    $startTime = date('Y-m-d H:00:00', strtotime('+30 minutes'));
5284                } else {
5285                    $startTime = date('Y-m-d H:30:00');
5286                }
5287                //check if teacher have next reservation
5288                $nextReservation = $this->LessonSchedule->find('count', array(
5289                        'conditions' => array(
5290                            'LessonSchedule.teacher_id' => $teacherId,
5291                            'LessonSchedule.status = 1',
5292                            'LessonSchedule.lesson_time' => $startTime
5293                        ),
5294                        'recursive' => -1
5295                ));
5296                $threeMinOtherBreakStartTime = $threeMinOtherBreakCurrentTime;
5297                //lesson prepare end time
5298                $endTime = strtotime($startTime . ' -1 minute');
5299                if ($nextReservation && ($threeMinOtherBreakCurrentTime + Configure::read('break_other_limit')) > $endTime) {
5300                    $threeMinOtherBreakStartTime = $endTime - Configure::read('break_other_limit');
5301                }
5302
5303                // set cookies
5304                $this->log(__METHOD__ . " Setting prepare other time : " . $threeMinOtherBreakStartTime, "debug");
5305                $this->Cookie->write('threeMinOtherBreakStartTime_'.$teacherId,  $threeMinOtherBreakStartTime, false);
5306            }
5307            $remainingTime = Configure::read('break_other_limit') - ($threeMinOtherBreakCurrentTime - $threeMinOtherBreakStartTime);
5308
5309            if ($remainingTime < 0) {
5310                $remainingTime = 0; // set to 0
5311            }
5312
5313            $mb = null;
5314
5315            // NC-5554 : get next meal break slot
5316            if (!$this->Auth->user('home_flg')) {
5317
5318                $lesson_time = date('Y-m-d H:00:00');
5319                if (date('i') >= 30) {
5320                    $lesson_time = date('Y-m-d H:30:00');
5321                }
5322
5323                // NC-5554 : Check the current slot time is meal break.
5324                $mb = $this->ShiftWorkMealBreak->useReplica()->find('first', array(
5325                    'fields' => array('ShiftWorkMealBreak.lesson_time'),
5326                    'conditions' => array(
5327                        'ShiftWorkMealBreak.teacher_id' => $this->Auth->user('id'),
5328                        'ShiftWorkMealBreak.lesson_time' => $lesson_time
5329                    )
5330                ));
5331            }
5332
5333            if ( $this->Auth->user('avatar_id') && in_array($this->Auth->user('avatar_id'), array_keys(Configure::read('avatar_teacher.id'))) ) {
5334                $onAirModel = 'LessonOnair';
5335                $teacherId = $this->Auth->user('id');
5336                $avatarIdList         = Configure::read('avatar_teacher.id');
5337                $avatarPhraseList    = Configure::read('avatar_teacher.phrases');
5338                $avatarName         = $avatarIdList[$this->Auth->user('avatar_id')];
5339                $avatarPhraseKeys     = array_keys($avatarPhraseList[$avatarName]);
5340                $showAvatarUsedPhrasesData = ['count' => [], 'phrases' => $avatarPhraseList[$avatarName]];
5341                $onAirLogsChatHash = $this->getCurrentChatHash($onAirModel, $teacherId);
5342                $chatHash = $this->Session->read('chatHash');
5343                $chatHash = isset($chatHash) ? $chatHash : $onAirLogsChatHash;
5344                //~ get avatar word phrase use count
5345                $this->loadModel('AvatarPhraseRecord');
5346                $this->AvatarPhraseRecord->openDBReplica();
5347                $getRecordedPhrase = $this->AvatarPhraseRecord->find('all', [
5348                    'fields' => [
5349                        'AvatarPhraseRecord.avatar_phrase_id',
5350                        'AvatarPhraseRecord.total'
5351                    ],
5352                    'conditions' => [
5353                        'AvatarPhraseRecord.avatar_phrase_id' => $avatarPhraseKeys,
5354                        'AvatarPhraseRecord.teacher_id' => $this->Auth->user('id'),
5355                        'AvatarPhraseRecord.chat_hash' => $chatHash
5356                    ],
5357                ]);
5358                $this->AvatarPhraseRecord->closeDBReplica();
5359
5360                foreach($getRecordedPhrase as $value) {
5361                    $showAvatarUsedPhrasesData['count'][$value['AvatarPhraseRecord']['avatar_phrase_id']] = $value['AvatarPhraseRecord']['total'];
5362                }
5363    
5364                $count = $showAvatarUsedPhrasesData['count'];
5365                $phrases = $showAvatarUsedPhrasesData['phrases'];
5366                $htmlModal = '';
5367
5368                foreach($phrases as $key => $data):
5369                    $total = isset($count[$key]) ? $count[$key] : 0;
5370                    $htmlModal .= '<tr class="'.($total <= 0 ? 'no_record' : 'has_record').'">';
5371                    $htmlModal .= '<td>' . $data['text'] . '</td>';
5372                    $htmlModal .= '<td>×</td>';
5373                    $htmlModal .= '<td>' . $total . '</td>';
5374                    $htmlModal .= '</tr>';
5375                endforeach;
5376            }
5377
5378            $returnData = array(
5379                'status' => true,
5380                'remainingTime' => $remainingTime,
5381                'shift_meal_break' => !empty($mb['ShiftWorkMealBreak']['lesson_time']) ? true : false,
5382                'avatar_phrase_html' => isset($htmlModal) ? $htmlModal : null
5383            );
5384            $this->Session->delete('chatHash');
5385        }
5386        return json_encode($returnData);
5387    }
5388
5389    /**
5390     * @api {get} /teacher/api/getImpendingAndOngoingReservation getImpendingAndOngoingReservation()
5391     * @apiName getImpendingAndOngoingReservation
5392     * @apiGroup API
5393     * @apiDescription used to check if there is an impending or ongoing reservation.
5394     * @apiSampleRequest off
5395     * 
5396      * @apiErrorExample {js} Used in: AngularJS
5397      * Location: "webroot/js/ng/controller/header.js"
5398     */
5399    public function getImpendingAndOngoingReservation() {
5400        $this->autoRender = false;
5401        return $this->LessonSchedule->checkImpendingAndOngoingReservation($this->Auth->user('id'));
5402    }
5403
5404    /**
5405     * @api {get} /teacher/api/deleteMemMinutesBreakRemainingTime deleteMemMinutesBreakRemainingTime()
5406     * @apiName deleteMemMinutesBreakRemainingTime
5407     * @apiGroup API
5408     * @apiDescription used to delete the teacher 3 minutes break remaining time.
5409     * @apiSampleRequest off
5410     * 
5411     * @apiSuccess {Boolean} status The status of the deletion.
5412     * @apiErrorExample {json} Success-Response:
5413     *     {
5414     *       "status": true
5415     *     }
5416     *
5417     * @apiErrorExample {json} Error-Response:
5418     *     {
5419     *       "status": false
5420     *     }
5421     */
5422    public function deleteMemMinutesBreakRemainingTime() {
5423        $this->autoRender = false;
5424        if ($this->request->is('get')) {
5425            $teacherId = $this->Auth->user('id');
5426            $threeMinOtherBreakStartTime = $this->Cookie->read('threeMinOtherBreakStartTime_'.$teacherId);
5427            if (!empty($threeMinOtherBreakStartTime)) {
5428                $this->Cookie->delete('threeMinOtherBreakStartTime_' . $teacherId);
5429                return json_encode(array('status' => true));
5430            }
5431            $this->log('hotfix_NC-3337: threeMinOtherBreakStartTime_'.$teacherId.' was not found.', 'debug');
5432        }
5433
5434        return json_encode(array('status' => false));
5435    }
5436
5437    /**
5438     * @api {post} /teacher/api/saveMemo saveMemo()
5439     * @apiName saveMemo
5440     * @apiGroup API
5441     * @apiDescription used to save the memo.
5442     * @apiSampleRequest off
5443     * 
5444     * @apiBody {String} id The ID of the user.
5445     * @apiBody {String} form_type The type of the form (form1 or form2).
5446     * @apiBody {String} date The date of the memo.
5447     * @apiBody {String} memo The memo content.
5448     * @apiBody {String} level The level for form2.
5449     * @apiBody {String} checklist The checklist for form2.
5450     * @apiBody {String} lessonId The lesson ID for form2.
5451     * @apiBody {String} recommendedTextbooks The recommended textbooks for form2.
5452     * @apiBody {String} lessonType The lesson type for form2.
5453     * @apiBody {String} lessonTrackId The lesson track ID.
5454     *
5455     * @apiSuccess {String} note The updated note.
5456     * @apiSuccess {Boolean} status The status of the request.
5457     * 
5458     * @apiErrorExample {json} Success-Response:
5459     *     {
5460     *       "note": "Updated note",
5461     *       "status": true
5462     *     }
5463     *
5464      * @apiErrorExample {js} Used in: AngularJS
5465      * Location: "webroot/js/ng/controller/student_info.js"
5466      * Location: "webroot/js/ng/app.js"
5467     */
5468    public function saveMemo() {
5469        $this->autoRender = false;
5470        if ($this->request->is('post')) {
5471            $data = $this->request->data;
5472            $id = $data['id'];
5473            $teacherId = $this->Auth->User('id');
5474            $teacherName = $this->Auth->User('jp_name');
5475            //return error message for invalid or null id
5476            if (!$id) {
5477                return json_encode(array('status' => false, 'content' => 'Invalid ID'));
5478            }
5479            $user = $this->User->find('first', array(
5480                'fields'=>array('note_counselor','note_textbook'),
5481                'conditions' => array('User.id' => $id),
5482                'recursive' => -1
5483            ));
5484
5485            //checks if date and nickname are empty or not
5486            $data['date'] = isset($data['date'])? $data['date'] : '';
5487            $userName['User']['nickname'] = isset($user['User']['nickname'])? $user['User']['nickname'] : '';
5488            $saveSelectedTextbook = true;
5489            //check if form type is equals to form 1 and also check if fields are empty or not
5490            if ($data['form_type'] == 'form1'){
5491                $data['memo'] = isset($data['memo'])? $data['memo'] : '';
5492                if($data['date'] == '' || $data['memo'] == ''){
5493                    return json_encode(array('status' => false, 'content' => 'Input Missing Fields'));
5494                }
5495            }
5496            //check if form type is equals to form 2 and also check if fields are empty or not
5497            if ($data['form_type'] == 'form2'){
5498                //validation for level check [maximum 7]
5499                if(isset($data['level']) && is_numeric($data['level']) && ($data['level'] > Configure::read('maximum_level_option'))) {
5500                    return json_encode(array('status' => false, 'content' => 'Level maximum of 7'));
5501                }
5502
5503                // NC-5729 get the Level
5504                if (isset($data['level']) && $data['level'] == '' || (isset($data['level']) && $data['level'] == '0')) {
5505                    $data['level'] = 'No Level Check';
5506                } else if (isset($data['level']) && $data['level'] === 'only_lvl_chck') {
5507                    $data['level'] = 'Only Level Check';
5508                } else if (isset($data['level']) && $data['level'] > 0) {
5509                    $data['level'] = $data['level'];
5510                } else if (!isset($data['level'])) {
5511                    $data['level'] = 'No Level Check';
5512                }
5513
5514                $data['checklist'] = isset($data['checklist'])? $data['checklist'] : '';
5515                if ($data['date'] == '' || $data['level'] == '' || $data['checklist'] == ''){
5516                    return json_encode(array('status' => false, 'content' => 'Input Missing Fields'));
5517                }
5518                $data['memo'] = "Level: ". $data['level']."\n".$data['checklist'];
5519                
5520                // NC-6757 Counseling save selected textbooks to counselor message form
5521                if ($data['lessonId']) {
5522                    $saveSelectedTextbookArr = array(
5523                        'lessonId' => $data['lessonId'],
5524                        'recommendedTextbooks' => $data['recommendedTextbooks'],
5525                        'lessonType' => $data['lessonType']
5526                    );
5527                    $this->log("NJ-4464 => saveMemo : " . json_encode($saveSelectedTextbookArr), "lesson_history_recovery");
5528                    $saveSelectedTextbook = $this->saveSelectedTextbook($saveSelectedTextbookArr);
5529                }
5530            }
5531            $lessonTrackId = (isset($data['lessonTrackId']) && $data['lessonTrackId']) ? $data['lessonTrackId'] : "";
5532            if ($data['form_type'] == 'form1'){
5533                $memo = $data['date'] . "【" . $teacherId . " " .$teacherName . "】" .$lessonTrackId. "\n" . $data['memo'] ."\n\n".$user['User']['note_counselor'];
5534                $saveField = 'note_counselor';
5535            }else{
5536                $memo = $data['date'] . "【" . $teacherId . " " .$teacherName . "】" .$lessonTrackId. "\n" . $data['memo'] ."\n\n".$user['User']['note_textbook'];
5537                $saveField = 'note_textbook';
5538            }
5539            $this->User->read(null, $id);
5540            if ($saveSelectedTextbook) {
5541                if($this->User->saveField($saveField, $memo)) {
5542                    return json_encode($memo);
5543                }
5544            }
5545        }
5546        return json_encode(array('status' => false));
5547    }
5548
5549    
5550    // NC-6757 Counseling save selected textbooks to counselor message form
5551    //only used in this controller
5552    public function saveSelectedTextbook ($params) {
5553        $this->autoRender = false;
5554
5555        if ($params) {
5556            $lessonId = $params['lessonId'];
5557            $lessonType = $params['lessonType'];
5558            $recommendedTextbooks = $params['recommendedTextbooks'];
5559            if ((isset($lessonId) && $lessonId) && (isset($recommendedTextbooks) && $recommendedTextbooks)) {
5560
5561                $updateParams = array(
5562                    'lessonId' => $lessonId,
5563                    'recommendedTextbooks' => $recommendedTextbooks
5564                );
5565
5566                if ($lessonType == 'onair') {
5567                    return $this->saveSelectedTextbookLessonOnAir($updateParams);
5568                } else if ($lessonType == 'onairLog') {
5569                    return $this->saveSelectedTextbookLessonOnAirLogs($updateParams);
5570                }
5571                return false;
5572
5573            }
5574            return false;
5575
5576        }
5577        return false;
5578    }
5579
5580    //only used in this controller
5581    public function saveSelectedTextbookLessonOnAir ($params) {
5582        $lessonOnair = $this->LessonOnair->find('first', array(
5583                'conditions' => array(
5584                    'LessonOnair.id' => $params['lessonId']
5585                ),
5586                'fields' => array(
5587                    'LessonOnair.id',
5588                    'LessonOnair.chat_hash',
5589                    'LessonOnair.lesson_memo'
5590                ),
5591                'recursive' => -1
5592            )
5593        );
5594
5595
5596        if ($lessonOnair) {
5597            $lessonMemo = $lessonOnair["LessonOnair"]["lesson_memo"];
5598            if ($lessonMemo != null) {
5599                $lessonMemoArr = json_decode($lessonMemo, true);
5600                $lessonMemoArr['message_19'] = array($params['recommendedTextbooks']);
5601            } else {
5602                $lessonMemoArr = array();
5603                $lessonMemoArr['message_10'] = array();
5604                $lessonMemoArr['message_19'] = array($params['recommendedTextbooks']);;
5605                $lessonMemoArr['message_20'] = array();
5606            }
5607
5608            $updateMemo = array(
5609                "id" => $lessonOnair["LessonOnair"]["id"],
5610                "lesson_memo" => json_encode($lessonMemoArr),
5611            );
5612
5613            $this->LessonOnair->clear();
5614            $this->LessonOnair->set($updateMemo);
5615            if ($this->LessonOnair->save()) {
5616                return true;
5617            }
5618            return false;
5619
5620        }
5621        return false;
5622
5623
5624    }
5625
5626    //only used in this controller
5627    public function saveSelectedTextbookLessonOnAirLogs ($params) {
5628        $lessonOnairsLog = $this->LessonOnairsLog->find('first', array(
5629                'conditions' => array(
5630                    'LessonOnairsLog.id' => $params['lessonId']
5631                ),
5632                'fields' => array(
5633                    'LessonOnairsLog.id',
5634                    'LessonOnairsLog.chat_hash',
5635                    'LessonOnairsLog.lesson_memo'
5636                ),
5637                'recursive' => -1
5638            )
5639        );
5640
5641        if ($lessonOnairsLog) {
5642            $lessonMemo = $lessonOnairsLog["LessonOnairsLog"]["lesson_memo"];
5643            if ($lessonMemo != null) {
5644                $lessonMemoArr = json_decode($lessonMemo, true);
5645                $lessonMemoArr['message_19'] = array($params['recommendedTextbooks']);
5646            } else {
5647                $lessonMemoArr = array();
5648                $lessonMemoArr['message_10'] = array();
5649                $lessonMemoArr['message_19'] = array($params['recommendedTextbooks']);;
5650                $lessonMemoArr['message_20'] = array();
5651            }
5652
5653            $updateMemo = array(
5654                "id" => $lessonOnairsLog["LessonOnairsLog"]["id"],
5655                "lesson_memo" => json_encode($lessonMemoArr),
5656            );
5657            $this->LessonOnairsLog->clear();
5658            $this->LessonOnairsLog->set($updateMemo);
5659            if ($this->LessonOnairsLog->save()) {
5660                return true;
5661            }
5662            return false;
5663
5664        }
5665        return false;
5666
5667    }
5668
5669    /**
5670     * @api {post} /teacher/api/updateMemo updateMemo()
5671     * @apiName updateMemo
5672     * @apiGroup API
5673     * @apiDescription used to update the memo.
5674     * @apiSampleRequest off
5675     * 
5676     * @apiBody {String} userId The ID of the user.
5677     * @apiBody {String} note The note to be saved.
5678     * @apiBody {String} noteType The type of the note (memo or textbook).
5679     *
5680     * @apiSuccess {String} note The updated note.
5681     * @apiSuccess {Boolean} status The status of the request.
5682     * 
5683     * @apiErrorExample {json} Success-Response:
5684     *     {
5685     *       "note": "Updated note",
5686     *       "status": true
5687     *     }
5688     *
5689     * @apiErrorExample {json} Error-Response:
5690     *     {
5691     *       "status": false
5692     *     }
5693     * 
5694      * @apiErrorExample {js} Used in: AngularJS
5695      * Location: "webroot/js/ng/controller/student_info.js"
5696     */
5697    public function updateMemo(){
5698        $this->autoRender = false;
5699        if ($this->request->is('post')) {
5700            $data = $this->request->data;
5701            if(isset($data['userId'])) {
5702                $userId = $data['userId'];
5703                // NC-5729 : @modified allow empty string to clear the notes.
5704                if (isset($data['note']) && isset($data['noteType'])) {
5705                    $note = $data['note'];
5706                    $saveField =  $data['noteType'] == 'memo' ? 'note_counselor' : 'note_textbook';
5707                    $this->User->read([$saveField], $userId);
5708                    if($this->User->saveField($saveField, strip_tags($note))){
5709                        return json_encode(array('note' => $note,'status' => true));
5710                    }
5711                }
5712            }
5713        }
5714        return json_encode(array('status' => false));
5715    }
5716
5717    /**
5718     * @api {post} /teacher/api/lessonPeriod lessonPeriod()
5719     * @apiName lessonPeriod
5720     * @apiGroup API
5721     * @apiDescription Get the lesson period of the user.
5722     * @apiSampleRequest off
5723     * 
5724     * @apiBody {String} userId The ID of the user.
5725     * @apiBody {String} startDate The start date of the lesson period.
5726     * @apiBody {String} endDate The end date of the lesson period.
5727     *
5728     * @apiSuccess {String} subcategories The subcategories of the user.
5729     * @apiError {Boolean} error The status of the request.
5730     * @apiError {String} content The content of the request.
5731     * 
5732     * @apiErrorExample {json} Success-Response:
5733     *     {
5734     *       "subcategories": "Category Subcategory: 3回<br>..."
5735     *     }
5736     *
5737     * @apiErrorExample {json} Error-Response:
5738     *     {
5739     *       "error": true,
5740     *       "content": "Lesson Period Date is required"
5741     *     }
5742     * 
5743      * @apiErrorExample {js} Used in: AngularJS
5744      * Location: "webroot/js/ng/controller/student_info.js"
5745     */
5746    public function lessonPeriod() {
5747        $this->autoRender = false;
5748        if ($this->request->is('post')) {
5749            $data = $this->request->data;
5750            $userId = $data['userId'];
5751            //check if date is not empty
5752            $startDate = isset($data['startDate'])? $data['startDate'] : '';
5753            $endDate =  isset($data['endDate'])? $data['endDate'] : '';
5754
5755            if($startDate == '' || $endDate == ''){
5756                return json_encode(array('error' => true, 'content' => 'Lesson Period Date is required'));
5757            }
5758
5759            $lessons = $this->LessonOnairsLog->find('all', array(
5760                    'fields' => array(
5761                        'TextbookSubCategory.name',
5762                        'TextbookCategory.name',
5763                        'Count(TextbookSubCategory.name) as times'
5764                    ),
5765                    'conditions'=> array(
5766                        'LessonOnairsLog.user_id' => $userId,
5767                        'LessonOnairsLog.start_time >= ? AND LessonOnairsLog.start_time <= ?' => array($startDate, $endDate),
5768                        'LessonOnairsLog.connect_id !=' => NULL
5769                    ),
5770                    'joins' => array (
5771                        array (
5772                            'type' => 'LEFT',
5773                            'table' => 'users',
5774                            'alias' => 'Users',
5775                            'conditions' => 'Users.id = LessonOnairsLog.user_id'
5776                        ),
5777                        array (
5778                            'type' => 'LEFT',
5779                            'table' => 'textbook_connects',
5780                            'alias' => 'TextbookConnect',
5781                            'conditions' => 'LessonOnairsLog.connect_id = TextbookConnect.id'
5782                        ),
5783                        array (
5784                            'type' => 'LEFT',
5785                            'table' => 'textbook_categories',
5786                            'alias' => 'TextbookCategory',
5787                            'conditions' => 'TextbookConnect.category_id = TextbookCategory.id'
5788                        ),
5789                        array(
5790                            'type' => 'LEFT',
5791                            'table' => 'textbook_subcategories',
5792                            'alias' => 'TextbookSubCategory',
5793                            'conditions' => 'TextbookSubCategory.id = TextbookConnect.subcategory_id'
5794                        ),
5795                    ),
5796                    'order' =>array('LessonOnairsLog.start_time' => 'DESC'),
5797                    'group' => array('TextbookSubCategory.id')
5798                )
5799            );
5800            //declare variable subcategories
5801            $subcategories = '';
5802            $textbooks = '';
5803            foreach ($lessons as $key => $lesson){
5804                $categoryName = $lesson['TextbookCategory']['name'];
5805                $textBook = $lesson['TextbookSubCategory']['name'];
5806                $count = $lesson[0]['times'];
5807                //return subcategories based on the lesson date period
5808                $subcategories .= $categoryName .' '. $textBook . ": " . $count . "回" ."<br>" ;
5809             }
5810            return $subcategories;
5811        }
5812    }
5813
5814    /**
5815     * @api {post} /teacher/api/checkTimezone checkTimezone()
5816     * @apiName checkTimezone
5817     * @apiGroup API
5818     * @apiSampleRequest off
5819    * @apiDescription used to check if the local timezone and timezone setting save is the same.
5820     *
5821     * @apiBody {String} timezoneName The name of the timezone to check.
5822     *
5823    * @apiSuccess {Boolean} status The status of the request.
5824    * @apiError {Boolean} status The status of the request.
5825    * @apiError {String} error The error message.
5826    * 
5827     * @apiErrorExample {json} Success-Response:
5828     *     {
5829     *       "status": true
5830     *     }
5831     *
5832     * @apiErrorExample {json} Error-Response:
5833     *     {
5834     *       "status": false,
5835     *       "error": "timezone_not_supported"
5836     *     }
5837    *
5838     * @apiErrorExample {js} Used in: AngularJS
5839     * Location: "webroot/js/ng/app.js"
5840     * Location: "webroot/js/ng/controller/dashboard.js"
5841     */
5842    /**
5843     * NC-3715
5844     * check if local timezone and timezone setting save is the same.
5845     * @param string $timezoneName
5846     * @return boolean json_encode array status true | false
5847     */
5848    public function checkTimezone() {
5849        $this->autoRender = false;
5850        $teacherId = $this->Auth->user('id');
5851
5852        $memcached = new myMemcached();
5853        $timezoneToken = $memcached->get('timezoneChangeNoticeFlg'.$teacherId);
5854
5855        // check if token for checking timezone at login is not exist
5856        if (!$timezoneToken) {
5857            return json_encode(array('status' => true));
5858        }
5859
5860        $memcached->delete('timezoneChangeNoticeFlg'.$teacherId);
5861
5862        // if not post request
5863        if (!$this->request->is('post')) {
5864            $this->log("TIMEZONE: request is not post", "debug");
5865            throw new Exception("invalid_request_type");
5866        }
5867
5868        if (!isset($this->request->data['timezoneName'])) {
5869            $this->log("TIMEZONE: param timezoneName missing.", "debug");
5870            return json_encode(array('status' => false, 'error' => 'timezone_not_supported'));
5871        }
5872
5873        // get current timezone name
5874        $currentTimezoneName = date_default_timezone_get();
5875
5876        $timezoneName = $this->request->data['timezoneName'];
5877        $tznArr = explode('/', $timezoneName);
5878        $continents = array_flip(Configure::read('continents'));
5879
5880        if (!isset($tznArr[0]) || (isset($tznArr[0]) && empty($continents[$tznArr[0]]))) {
5881            $this->log('Timezone not supported. teacher_id: ' . $teacherId. ' post data --> ' . json_encode($this->request->data), 'timezone_debug');
5882            return json_encode(array('status' => false, 'error' => 'timezone_not_supported'));
5883        }
5884        //set city
5885        $city = isset($tznArr[1]) ? $tznArr[1] : '';
5886        $city .= isset($tznArr[2]) ? '/' . $tznArr[2] : '';
5887
5888        date_default_timezone_set($timezoneName);
5889        $userTz = new DateTimeZone($timezoneName);
5890        $userTime = new DateTime('now', $userTz);
5891
5892        $offset = date_offset_get(new DateTime);
5893        $daylightSavingTime = date('I');
5894
5895        $localTime = strtotime($userTime->format('Y-m-d H:i:s'));
5896        $localDateDisplay = $userTime->format('d/m/Y(D) g:i A');
5897
5898        $serverTz = new DateTimeZone($currentTimezoneName);
5899        $serverTime = new DateTime('now', $serverTz);
5900        $serverTime =  strtotime($serverTime->format('Y-m-d H:i:s'));
5901        date_default_timezone_set($currentTimezoneName);
5902
5903        //adjust to timezone
5904        if ($daylightSavingTime) {
5905            $offset -= 3600;
5906            $localTime -= 3600;
5907        }
5908
5909        $timezoneModel = $this->Timezone;
5910
5911        // get timezone id
5912        $params = array(
5913            'type' => 'first',
5914            'args' => array(
5915                'fields' => array(
5916                    'id',
5917                    'utc_offset',
5918                    'city_eng',
5919                    'country_id'
5920                ),
5921                'conditions' => array(
5922                    'continent_id' => $continents[$tznArr[0]],
5923                    'city_eng' => $city
5924                )
5925            )
5926        );
5927
5928        $timezone = $timezoneModel->getTimezones($params);
5929        $sameTimezone = false;
5930        $timezoneSetDisplay = '';
5931        
5932        $mem = new myMemcached();
5933        $memTimezone = $mem->get('timezone'.$teacherId);
5934
5935        if (empty($timezone)) {
5936            $timeDiff = ($localTime - $serverTime)/60;
5937            $utcOffset = myTools::offsetFormat($offset/60);
5938            $timezonesAndCountryCodes = $this->Timezone->getTimezonesAndCountryCodes();
5939            $countryCode = isset($timezonesAndCountryCodes[$timezoneName]) ? $timezonesAndCountryCodes[$timezoneName] : null;
5940
5941            // log if timezone not exist on timezones and country codes list.
5942            if (!$countryCode) {
5943                $this->log('Timezone does not exist on timezonesAndCountryCodes list. teacher_id: ' . $teacherId . ' --> ' . json_encode($timezoneName) . ' TZ_params: ' . json_encode($this->request->data), 'timezone_debug');
5944            }
5945
5946            $saveData = array(
5947                'continent_id' => $continents[$tznArr[0]],
5948                'city_eng' => $city, // city
5949                'city_jp' => $city, // city
5950                'country_code_id' => $countryCode,
5951                'utc_offset' => $utcOffset,
5952                'jp_time_diff' => $timeDiff
5953            );
5954
5955            $timezone = $timezoneModel->saveTimeZone($saveData);
5956
5957            // if successfully save new timezone
5958            if ($timezone) {
5959                // set reset timezones memcached
5960                $memcached->set(array(
5961                    'key' => 'reset_timezones_memcached',
5962                    'value' => true,
5963                ));
5964                
5965                // - NJ-3653 if have new timezone delete select_all_country_codes memcache
5966                $memKey = 'select_all_country_codes';
5967                $mem->delete($memKey);
5968                // - after deleting timezon memcache add new one
5969                $this->Timezone->countryOptions();
5970            }
5971        } else {
5972            // check if local timezone is same to the current timezone setting.
5973            if (
5974                !empty($memTimezone) && $memTimezone['continent_id'] == $continents[$tznArr[0]] &&
5975                $memTimezone['city_eng'] == $tznArr[1] && $this->Auth->user('timezone_dst_flg') == $daylightSavingTime
5976            ) {
5977                $sameTimezone = true;
5978            } else {
5979                $timeDiff = myTools::timestampWithTimeDiff($localTime, $this->timeDiff);
5980                $timezoneSetDisplay = date('d/m/Y(D) g:i A', $timeDiff) . ' ('.$memTimezone['city_eng'].', '.$memTimezone['country_name'].' UTC'.$memTimezone['utc_offset'].')';
5981            }
5982        }
5983
5984        // update teacher timezone data if timezone_id is null
5985        $timezoneId = $this->Auth->user('timezone_id');
5986        if (empty($timezoneId)) {
5987            $this->Teacher->updateTeacher(
5988                array('timezone_id' => $timezone['Timezone']['id'], 'timezone_dst_flg' => $daylightSavingTime),
5989                $teacherId,
5990                true
5991            );
5992
5993            $params = array(
5994                'id' => $timezone['Timezone']['id'],
5995                'teacherId' => $teacherId
5996            );
5997            $this->Timezone->memTeacherTimezone($params);
5998            $sameTimezone = true;
5999        } else {
6000            $timeDiff = myTools::timestampWithTimeDiff($localTime, $this->timeDiff);
6001            $timezoneSetDisplay = date('d/m/Y(D) g:i A', $timeDiff).' ('.$memTimezone['city_eng'].', '.$memTimezone['country_name'].' UTC'.$memTimezone['utc_offset'].')';
6002        }
6003
6004        if ($sameTimezone) {
6005            return json_encode(array('status' => true));
6006        } else {
6007            $this->log('teacher_id: ' . $teacherId . 
6008                'TZ_params: ' . json_encode($this->request->data) . 
6009                ' user_timezone: ' . json_encode($timezone) . 
6010                ' daylightSavingTime: ' . json_encode($daylightSavingTime) . 
6011                ' localDateDisplay: ' . json_encode($localDateDisplay) .
6012                ' timezoneSetDisplay: ' . json_encode($timezoneSetDisplay)
6013            , 'timezone_debug');
6014            return json_encode(array(
6015                'status' => false,
6016                'data' => array(
6017                    'timezone_id' => $timezone['Timezone']['id'],
6018                    'timezone_dst_flg' => $daylightSavingTime,
6019                    'localDateDisplay' => $localDateDisplay,
6020                    'timezoneSetDisplay' => $timezoneSetDisplay
6021                )
6022            ));
6023        }
6024    }
6025
6026    /**
6027     * @api {post} /teacher/api/updateTeacherTimezone updateTeacherTimezone()
6028     * @apiName updateTeacherTimezone
6029     * @apiGroup API
6030     * @apiDescription Update the teacher timezone.
6031     * @apiSampleRequest off
6032     * 
6033     * @apiBody {Number} timezone_id The ID of the new timezone.
6034     * @apiBody {Boolean} timezone_dst_flg The DST flag for the new timezone.
6035     *
6036     * @apiSuccess {Boolean} success The status of the request.
6037     * @apiError {Boolean} success The status of the request.
6038     * @apiError {Boolean} excessSlotsFlg The status of the request.
6039     * 
6040     * @apiErrorExample {json} Success-Response:
6041     *     {
6042     *       "success": true
6043     *     }
6044     *
6045     * @apiErrorExample {json} Error-Response:
6046     *     {
6047     *       "success": false,
6048     *       "excessSlotsFlg": true
6049     *     }
6050     * 
6051      * @apiErrorExample {js} Used in: AngularJS
6052      * Location: "webroot/js/ng/app.js"
6053     */
6054    /**
6055     * NC-3715
6056     * update teacher timezone
6057     * post request
6058     * @return boolean json_encode array success true | false
6059     */
6060    public function updateTeacherTimezone() {
6061        $this->autoRender = false;
6062        $res = false;
6063
6064        // if not post request
6065        if (!$this->request->is('post')) {
6066            $this->log("TIMEZONE: request is not post", "debug");
6067            throw new Exception("invalid_request_type");
6068        }
6069
6070        $data = $this->request->data;
6071
6072        // if missing parameter(s)
6073        if (!isset($data['timezone_id']) || !isset($data['timezone_dst_flg'])) {
6074            $this->log("TIMEZONE: missing parameter(s)", "debug");
6075            throw new Exception("TIMEZONE: missing parameter(s).");
6076        }
6077        
6078        // if new timezone > 24 slots
6079        $precheck = $this->ShiftWorkOn->preCheckTeacherSlotCount($data['timezone_id'], $this->Auth->user('id'));
6080        if ($precheck) {
6081            $this->log("TIMEZONE: Update to update to new timezone.", "debug");
6082            return json_encode(array('success' => false, 'excessSlotsFlg' => true));
6083        }
6084
6085        $removeValidation = true;
6086        $teacherId = $this->Auth->user('id');
6087
6088        if ($this->Teacher->updateTeacher($data, $teacherId, $removeValidation)) {
6089            $res = true;
6090
6091            $memcached = new myMemcached();
6092
6093            // delete current memcached for japan timezone difference
6094            $memcached->delete('timezoneChangeNoticeFlg'.$teacherId);
6095
6096            $params = array(
6097                'id' => $data['timezone_id'],
6098                'dst' => $data['timezone_dst_flg'],
6099                'teacherId' => $teacherId
6100            );
6101
6102            // set memcached time difference
6103            $this->Timezone->memTeacherTimezone($params);
6104        }
6105
6106        return json_encode(array('success' => $res));
6107    }
6108
6109    /**
6110     * @api {post} /teacher/api/getServerAndLocalTime getServerAndLocalTime()
6111     * @apiName getServerAndLocalTime
6112     * @apiGroup API
6113     * @apiDescription Get server and local time.
6114     * @apiSampleRequest off
6115     * 
6116     * @apiSuccess {Number} serverTimeParsed The server time parsed.
6117     * @apiSuccess {String} serverTime The server time.
6118     * @apiSuccess {String} serverTimeFormatted The server time formatted.
6119     * @apiSuccess {String} localTime The local time.
6120     * @apiSuccess {String} localFlag The local flag.
6121     * @apiSuccess {String} localTimeFormatted The local time formatted.
6122     * @apiSuccess {String} localTime12H The local time 12H.
6123     * @apiSuccess {String} serverTime12H2 The server time 12H.
6124     * 
6125     * @apiErrorExample {json} Success-Response:
6126     *     {
6127     *       "serverTimeParsed": 1633024800,
6128     *       "serverTime": "Oct 01,2021",
6129     *       "serverTimeFormatted": "10:00 AM",
6130     *       "localTime": "Oct 01,2021",
6131     *       "localFlag": "/user/images/flag/other.png",
6132     *       "localTimeFormatted": "10:00 AM",
6133     *       "localTime12H": "10:00:AM",
6134     *       "serverTime12H2": "10:00:AM"
6135     *     }
6136     *
6137      * @apiErrorExample {js} Used in: AngularJS
6138      * Location: "webroot/js/ng/controller/account.js"
6139      * Location: "webroot/js/ng/controller/home.js"
6140      * Location: "webroot/js/ng/controller/recruit.js"
6141      * Location: "webroot/js/ng/app.js"
6142      * Location: "webroot/js/ng/home.js"
6143     */
6144    /**
6145     * NC-3715
6146     * get serG2150er and local time
6147     * @return mixed json encoded array
6148     */
6149    public function getServerAndLocalTime() {
6150        $this->autoRender = false;
6151
6152        // if not post request
6153        if (!$this->request->is('post')) {
6154            $this->log("TIMEZONE: request is not post", "debug");
6155            throw new Exception("invalid_request_type");
6156        }
6157
6158        /* if office teacher 
6159            NC-9998 : comment this block of code
6160        */
6161        // if (!$this->Auth->user('home_flg')) {
6162        //     return json_encode(array('status' => false));
6163        // }
6164
6165        $teacherID = $this->Auth->user('id');
6166        //NJ-11421 : get teacher time zone data 
6167        $localFlag = "other.png";
6168
6169        //find the country code data 
6170
6171        $this->Teacher->openDBReplica();
6172
6173        $countryData = $this->Teacher->find('first',array(
6174            'fields' => array(
6175                    'Teacher.id',
6176                    'CountryCode.country_name'
6177            ),
6178            'joins' => array(
6179                array(
6180                    'table' => 'timezones',
6181                    'alias' => 'Timezone',
6182                    'type' => 'LEFT',
6183                    'conditions' => array('Timezone.id = Teacher.timezone_id')
6184                ),
6185                array(
6186                    'table' => 'country_codes',
6187                    'alias' => 'CountryCode',
6188                    'type' => 'LEFT',
6189                    'conditions' => array('Timezone.country_code_id = CountryCode.code')
6190                )
6191            ),
6192            'conditions' => array('Teacher.id' => $teacherID),
6193            'recursive' => -1
6194        ));
6195
6196        $this->Teacher->closeDBReplica();
6197
6198
6199        $localFlag = (isset($countryData['CountryCode']['country_name'])) ? strtolower(str_replace(' ', '_', $countryData['CountryCode']['country_name'])) . '.png' : $localFlag;
6200
6201
6202        if (!file_exists(ROOT . "/user/webroot/images/flag/" . $localFlag)) {
6203            $localFlag = 'other.png';
6204        }
6205
6206        $localFlag = '/user/images/flag/'.$localFlag;
6207
6208
6209        $datetime = date('Y-m-d H:i:s');
6210        $serverTime = date('M d,Y');
6211        $rawLocalTime = myTools::timestampWithTimeDiff($datetime, $this->timeDiff);
6212        $localTime = myTools::datetimeFormatted($rawLocalTime);
6213        $localTime12H = date('g:i A',$rawLocalTime);
6214        $serverTime12H = date('g:i A');
6215        $serverTime12H2 = date('g:i:A');
6216        $seconds = date('s');
6217
6218        $returnData =  array(
6219            'serverTimeParsed' => strtotime($datetime),
6220            'serverTime' => $serverTime,
6221            'serverTimeFormatted' => $serverTime12H, 
6222            'localTime' => date("M d,Y",$rawLocalTime),
6223            'localFlag' => $localFlag,
6224            'localTimeFormatted' => $localTime12H,
6225            'localTime12H' => date('g:i:A',$rawLocalTime),
6226            'serverTime12H2' => $serverTime12H2,
6227        );
6228
6229        return json_encode($returnData);
6230    }
6231
6232    /**
6233     * @api {post} /teacher/api/checkDaylightSavingTime checkDaylightSavingTime()
6234     * @apiName checkDaylightSavingTime
6235     * @apiGroup API
6236     * @apiDescription Return 1 if post data timezone name does have daylight saving time, else 0
6237     * @apiSampleRequest off
6238     * 
6239     * @apiBody {String} timezoneName The name of the timezone to check for DST.
6240     *
6241     * @apiSuccess {Number} dst The DST flag.
6242     * 
6243     * @apiErrorExample {json} Success-Response:
6244     *     {
6245     *       "dst": 1
6246     *     }
6247     *
6248     * @apiErrorExample {json} Error-Response:
6249     *     {
6250     *       "dst": 0
6251     *     }
6252     * 
6253      * @apiErrorExample {js} Used in: AngularJS
6254      * Location: "webroot/js/ng/controller/account.js"
6255      * Location: "webroot/recruitment/js/common.js"
6256     */
6257    /**
6258     * Return 1 if post data timezone name does have daylight saving time, else 0
6259     */
6260    public function checkDaylightSavingTime() {
6261        $this->autoRender = false;
6262        $dst = 0;
6263        if ($this->request->is('post')) {
6264            $postData = $this->request->data;
6265            $dst = myTools::getDaylightSavingTime($postData['timezoneName']);
6266        }
6267
6268        return json_encode(array('dst' => $dst));
6269    }
6270
6271    //only used in this controller
6272    public function getOpenedSlot() {
6273        $this->autoRender = false;
6274        return $this->ShiftWorkOn->checkOpenedSlot($this->Auth->user('id'));
6275    }
6276
6277    /**
6278     * @api {post} /teacher/api/deleteEqualTo5MinutesAndBelowModal deleteEqualTo5MinutesAndBelowModal()
6279     * @apiName deleteEqualTo5MinutesAndBelowModal
6280     * @apiGroup API
6281     * @apiDescription Delete memcache key EqualTo5MinutesAndBelowModal_teacher_id
6282     * @apiSampleRequest off
6283     * 
6284     * @apiSuccess {Boolean} success The status of the request.
6285     * 
6286     * @apiErrorExample {json} Success-Response:
6287     *     {
6288     *       "success": true
6289     *     }
6290     *
6291     * @apiErrorExample {json} Error-Response:
6292     *     {
6293     *       "success": false
6294     *     }
6295     * 
6296     * @apiErrorExample {js} Used in: Elements
6297     * Location: "view/Elements/chat_area_js.php"
6298     */
6299    /**
6300     * NC-4027
6301     * memcache delete EqualTo5MinutesAndBelowModal_[teacher_id]
6302     * return boolean true | false
6303     */
6304    public function deleteEqualTo5MinutesAndBelowModal() {
6305        $this->autoRender = false;
6306        $teacherId = $this->Auth->user('id');
6307
6308        // if not ajax request
6309        if (!$this->request->is('ajax')) {
6310            $this->log("[3_MINUTES_BREAKTIME] request is not AJAX", "debug");
6311            throw new Exception("invalid_request_type");
6312        }
6313
6314        // add user to memcached array
6315        $memcached = new myMemcached();
6316
6317        // delete memcached
6318        $memcached->delete('EqualTo5MinutesAndBelowModal_' . $teacherId);
6319
6320        // check if successfully deleted
6321        if (!$memcached->get('EqualTo5MinutesAndBelowModal_' . $teacherId)) {
6322            $success = true;
6323        } else {
6324            $success = false;
6325        }
6326
6327        return json_encode(array('success' => $success));
6328    }
6329
6330    /**
6331     * @api {post} /teacher/api/updateCounselingAttendedFlg updateCounselingAttendedFlg()
6332     * @apiName updateCounselingAttendedFlg
6333     * @apiGroup API
6334     * @apiDescription Update the counseling attended flag.
6335     * @apiSampleRequest off
6336     * 
6337     * @apiBody {String} userId The ID of the user.
6338     * @apiBody {String} counselingAttendedFlg The flag to be saved.
6339     * 
6340     * @apiSuccess {Boolean} success The status of the request.
6341     * 
6342     * @apiErrorExample {json} Success-Response:
6343     *     {
6344     *       "success": true
6345     *     }
6346     *
6347     * @apiErrorExample {json} Error-Response:
6348     *     {
6349     *       "success": false
6350     *     }
6351     * 
6352      * @apiErrorExample {js} Used in: AngularJS
6353      * Location: "webroot/js/ng/controller/student_info.js"
6354     */
6355    public function updateCounselingAttendedFlg(){
6356        $this->autoRender = false;
6357        $result['success'] = false;
6358        if ($this->request->is('post')) {
6359            $data = $this->request->data;
6360            $this->User->read(array('counseling_attended_flg'), $data['userId']);
6361            $this->User->validate = false;
6362            $this->User->set(array('counseling_attended_flg' => $data['counselingAttendedFlg']));
6363            $this->User->save();
6364            $result['success'] = true;
6365        }
6366        return json_encode($result);
6367    }
6368
6369    /**
6370     * @api {get} /teacher/api/checkReservation checkReservation()
6371     * @apiName checkReservation
6372     * @apiGroup API
6373     * @apiDescription Check if teacher has reservation.
6374     * @apiSampleRequest off
6375     * 
6376     * @apiSuccess {Boolean} status The status of the request.
6377     * 
6378     * @apiErrorExample {json} Success-Response:
6379     *     {
6380     *       "status": "true"
6381     *     }
6382     *
6383     * @apiErrorExample {json} Error-Response:
6384     *     {
6385     *       "status": "false"
6386     *     }
6387     * 
6388      * @apiErrorExample {js} Used in: AngularJS
6389      * Location: "webroot/js/ng/controller/header.js"
6390      * Location: "webroot/js/ng/app.js"
6391      * Location: "webroot/js/recruitment/ng/app.js"
6392     */
6393    //NC-4522 add alert if teacher has reservation
6394    public function checkReservation() {
6395        $this->autoRender = false;
6396        if ($this->request->is('get')) {
6397            $teacherId = $this->Auth->user('id');
6398            $curTime = date("Y-m-d H:i:00", strtotime('-25 minute'));
6399            $countReservation = $this->LessonSchedule->find('count', array(
6400                'conditions' => array(
6401                    array('LessonSchedule.teacher_id' => $teacherId),
6402                    array('LessonSchedule.status' => Configure::read("lesson_schedule.status.on_reserve")),
6403                    array('LessonSchedule.lesson_time >=' => $curTime)
6404                )
6405            ));
6406            $hasReservation = "false";
6407            $memcached = new myMemcached();
6408            if ($memcached->get('teacherHasReservation_' . $teacherId) && $countReservation > 0) {
6409                $hasReservation = "true";
6410            }
6411            $memcached->delete('teacherHasReservation_' . $teacherId);
6412            return json_encode(array("status" => $hasReservation));
6413        }
6414        return json_encode(array("status" => "false"));
6415    }
6416
6417    // strip double curly braces for angularjs and sanitize inputs
6418    private function sanitizeInputs( $params = array() ){
6419        $result = array();
6420        if ( isset($params["data"]) && count($params["data"]) > 0 ) {
6421            foreach ($params["data"] as $key => $value) {
6422                $doubleCurlyBracesFilter = myTools::doubleCurlyBraceFilter($value);
6423                $result[$key] = $doubleCurlyBracesFilter;
6424            }
6425        } else {
6426            $result = $params["data"];
6427        }
6428        return $result;
6429    }
6430
6431    /**
6432     * @api {post} /teacher/api/getPopupList getPopupList()
6433     * @apiName getPopupList
6434     * @apiGroup API
6435     * @apiDescription Get the list of popups.
6436     * @apiSampleRequest off
6437     * 
6438     * @apiBody {String} material_category_id The ID of the material category.
6439     *
6440     * @apiSuccess {Array} popupList The list of popups.
6441     * 
6442     * @apiErrorExample {json} Success-Response:
6443     *     {
6444     *       "popupList": [...]
6445     *     }
6446     * 
6447     * @apiErrorExample {js} Used in: Elements
6448     * Location: "view/Elements/chat_area_js.php"
6449     */
6450    /**
6451    * NC-5253
6452    */
6453    public function getPopupList () {
6454        $this->autoRender = false;
6455        if ($this->request->is('post', 'ajax')) {
6456            $data = $this->request->data;
6457            $return = array();
6458            if (isset($data['material_category_id'])) {
6459                $params['conditions'] = array(
6460                    'LessonPopup.material_category_id' => $data['material_category_id']
6461                );
6462                $popUp = $this->LessonPopup->getLessonPopups($params);
6463                $return = $this->LessonPopup->segregateLessonPopups($popUp);
6464            }
6465            return json_encode($return);
6466        }
6467    }
6468
6469    /**
6470     * @api {post} /teacher/api/getTextbookCategoryId getTextbookCategoryId()
6471     * @apiName getTextbookCategoryId
6472     * @apiGroup API
6473     * @apiDescription Get the category ID using the connect ID.
6474     * @apiSampleRequest off
6475     * 
6476     * @apiBody {String} connectId The ID of the textbook connection.
6477     *
6478     * @apiSuccess {Number} category_id The category ID.
6479     * 
6480     * @apiErrorExample {json} Success-Response:
6481     *     {
6482     *       "category_id": 1
6483     *     }
6484     *
6485      * @apiErrorExample {js} Used in: AngularJS
6486      * Location: "webroot/js/webrtcv2/event.common.js"
6487     */
6488    /**
6489    * NC-5253 get category_id using connectId
6490    * @param int connectId,
6491    * @return int category_id
6492    */
6493    public function getTextbookCategoryId () {
6494        $this->autoRender = false;
6495        if ($this->request->is('post', 'ajax')) {
6496            $rdata = $this->request->data;
6497            $return = array();
6498
6499            if (isset($rdata['connectId'])) {
6500                $data = $this->TextbookConnect->useReplica()->find('first', array(
6501                    'fields' => array('TextbookConnect.category_id'),
6502                    'conditions' => array('TextbookConnect.id' => $rdata['connectId'])
6503                ));
6504                $return = isset($data['TextbookConnect']['category_id']) ? $data['TextbookConnect'] : $return ;
6505            }
6506
6507            return json_encode($return);
6508        }
6509    }
6510
6511    /**
6512    * Post prohibited word to slack
6513    */
6514    private function postSlack($slackData) {
6515        $mySlack = new mySlack();
6516        $mySlack->allow_test_channel = true;
6517        $mySlack->channel = myTools::checkChannel("#nc-prohibited-word", "#fdc-test-channel");
6518        $teacherTypeLabel = "office";
6519        $mySlack->link_names = true;
6520
6521        //check teacher type for tag recipients
6522        if ($slackData['homeFlag']) {
6523            //@kijima @nc-kawanami @nc-inanobe (for home-based teachers)
6524            $mySlack->text = "<!subteam^SQ1FJV0P2|group-home-prohibited-word>";
6525            $teacherTypeLabel = "home";
6526        } else {
6527            //@funahashi @nc-ueda  @kawahara @nc-takauji @nc-sawada (for office-based  teachers)
6528            $mySlack->text = "<!subteam^SQ11WAUUU|group-office-prohibited-word>";
6529        }
6530
6531        # Get the base url 
6532        $baseUrl = myTools::getBaseUrl();
6533
6534        # Initialize variable for creating slact text with link
6535        $teacherLink = "<$baseUrl/admin/instructor-manage/instructor/{$slackData['teacherId']}|{$slackData['teacherId']}>";
6536        $studentLink = "<$baseUrl/admin/user-manage/member/{$slackData['userId']}|{$slackData['userId']}>";
6537        $lessonLink = "<$baseUrl/admin/lesson-history?lesson_track_id={$slackData['lesson_number']}|{$slackData['lesson_number']}>";
6538        $messageLink = "";
6539
6540        # Customize Link for Message Management
6541        if(!empty($slackData['lesson_id'])) {
6542            if($slackData['is_lesson_onair']) {
6543                $lessonOnAir = $this->LessonOnair->find('first', [
6544                    'conditions' => ['LessonOnAir.id' => $slackData['lesson_id']],
6545                    'fields' => ['LessonOnAir.chat_hash']
6546                ]);
6547                if(!empty($lessonOnAir)) {
6548                    $messageLink = "<$baseUrl/admin/message-manage?teacher_id={$slackData['teacherId']}&user_id={$slackData['userId']}&chat_hash={$lessonOnAir['LessonOnair']['chat_hash']}|{$lessonOnAir['LessonOnair']['chat_hash']}>";
6549                }
6550            } else {
6551                $lessonOnAir = $this->LessonOnairsLog->find('first', [
6552                    'conditions' => ['LessonOnairsLog.id' => (int)$slackData['lesson_id']],
6553                    'fields' => ['LessonOnairsLog.chat_hash']
6554                ]);
6555                if(!empty($lessonOnAir)) {
6556                    $messageLink = "<$baseUrl/admin/message-manage?teacher_id={$slackData['teacherId']}&user_id={$slackData['userId']}&chat_hash={$lessonOnAir['LessonOnairsLog']['chat_hash']}|{$lessonOnAir['LessonOnairsLog']['chat_hash']}>";
6557                }
6558            }
6559        }
6560
6561        $mySlack->text .= "```";
6562        $mySlack->text .= "【Prohibited word was sent!】\n";
6563        $mySlack->text .= "Date: " . date('Y-m-d') . "\n";
6564        $mySlack->text .= "Teacher name : " . $slackData['teacherName'] . "\n";
6565        $mySlack->text .= "Teacher ID : " . $teacherLink . "\n";
6566        $mySlack->text .= "Teacher Type : " . $teacherTypeLabel . "\n";
6567        $mySlack->text .= "Student name : " . $slackData['userName'] . "\n";
6568        $mySlack->text .= "Student ID : " . $studentLink . "\n";
6569        $mySlack->text .= "Lesson ID : " . $lessonLink . "\n";
6570        $mySlack->text .= "Sent message : " . $messageLink . "\n";
6571        $mySlack->text .= "\n" . $slackData['comment'] . "\n";
6572        $mySlack->text .= "```";
6573        $mySlack->sendSlack();
6574    }
6575
6576    /**
6577    * NC-8372 : chivox error
6578    */
6579    private function sendChivoxSlack ($params = array()) {
6580        //initialize slack and send
6581        $mySlack = new mySlack();
6582        $content = "";
6583        $chivoxError = isset($params['error']) ? $params['error'] : "";
6584
6585        // Total
6586        $content .= "Error Content :" . "\n";
6587        $content .= $chivoxError . "\n";
6588        $content .= "Logs : " . "\n";
6589        $content .= json_encode($params);
6590
6591        $params = array("content" => $content);
6592        $mySlack->chivoxReport($params);
6593    }
6594
6595    /**
6596     * @api {post} /teacher/api/saveAdminStaffEvaluation saveAdminStaffEvaluation()
6597     * @apiName saveAdminStaffEvaluation
6598     * @apiGroup API
6599     * @apiDescription Save the admin staff evaluation.
6600     * @apiSampleRequest off
6601     * 
6602     * @apiBody {Number} rate The rate of the evaluation.
6603     * @apiBody {String} comment The comment of the evaluation.
6604     * @apiBody {Number} admin_id The ID of the admin.
6605     * @apiBody {Number} inquiry_id The ID of the inquiry.
6606     * 
6607     * @apiSuccess {Boolean} success The status of the request.
6608     * @apiSuccess {Boolean} exist The status of the evaluation.
6609     * @apiSuccess {Number} rate The rate of the evaluation.
6610     * 
6611     * @apiErrorExample {json} Success-Response:
6612     *     {
6613     *       "success": true,
6614     *       "exist": true,
6615     *       "rate": 5
6616     *     }
6617     *
6618      * @apiErrorExample {js} Used in: AngularJS
6619      * Location: "webroot/js/ng/app.js"
6620     */
6621    public function saveAdminStaffEvaluation() {
6622        $this->autoRender = false;
6623        $response['success'] = false;
6624        $teacherId =  $this->Auth->user('id');
6625        $home_flg = $this->Auth->user('home_flg');
6626
6627        if ($this->request->is('post')) {
6628            $data = $this->request->data;
6629
6630            $saveData = array(
6631                'member_id' => $teacherId,
6632                'evaluation_category' => 4,
6633                'rate' => $data['rate'],
6634                'comment' => $data['comment'],
6635                'member_type' => ($home_flg) ? 2 : 4,
6636                'admin_id' => $data['admin_id'],
6637                'inquiry_id' => $data['inquiry_id']
6638            );
6639            $evaluationChecker = myTools::evaluationChecker(array('category' => 4, 'inquiry_id' => $data['inquiry_id']));
6640            if (isset($evaluationChecker['AdminStaffEvaluation']['rate'])) {
6641                return json_encode(array('success' => true, 'exist' =>  true, 'rate' => $evaluationChecker['AdminStaffEvaluation']['rate']));
6642            } else {
6643                return $this->AdminStaffEvaluation->evaluate($saveData);
6644            }
6645        }
6646        return json_encode($response);
6647    }
6648
6649    /**
6650     * @api {get} /teacher/api/getAnnouncement getAnnouncement()
6651     * @apiName getAnnouncement
6652     * @apiGroup API
6653     * @apiDescription Get the announcement for the teacher.
6654     * @apiSampleRequest off
6655     * 
6656     * @apiBody {String} id The ID of the teacher.
6657     *
6658     * @apiSuccess {Array} announcement The list of announcements.
6659     * 
6660     * @apiErrorExample {json} Success-Response:
6661     *     {
6662     *       "announcement": [...]
6663     *     }
6664     *
6665      * @apiErrorExample {js} Used in: AngularJS
6666      * Location: "webroot/js/ng/home.js"
6667     */
6668    public function getAnnouncement($id) {
6669        $this->autoRender = false;
6670
6671        if (!isset($id) || !ctype_digit($id)){
6672            return false;
6673        }
6674
6675        $teacher = $this->Teacher->find('first', array(
6676            'conditions' => array('Teacher.id' => $id),
6677            'recursive' => -1
6678            )
6679        );
6680
6681        if($teacher){
6682            $now = date('Y-m-d H:i:00');
6683            $type = $teacher['Teacher']['rank_coin_id'];
6684            $id = $teacher['Teacher']['id'];
6685            $home_flg = $teacher['Teacher']['home_flg'];
6686            
6687            $teacherGroupId = $this->TeacherGroupLink->query("SELECT DISTINCT link_id from teacher_groups_links where status = 1 and type = 1 and teacher_id = $id");
6688            $groupIds = array();
6689            $query = array();
6690            if(!empty($teacherGroupId[0]['teacher_groups_links']['link_id'])) {
6691                foreach ($teacherGroupId as $g_id) {
6692                    $groupIds[] = $g_id['teacher_groups_links']['link_id'];    
6693                }
6694                $query[] = array('TeacherAnnouncement.id IN' => $groupIds);
6695            }
6696
6697            $this->TeacherAnnouncement->openDBReplica();
6698            $announcement = $this->TeacherAnnouncement->find('all', array(
6699                'conditions' => array(
6700                    'TeacherAnnouncement.start_time <=' => $now,
6701                    'TeacherAnnouncement.end_time >=' => $now,
6702                    'TeacherAnnouncement.display' => 1,
6703                    'TeacherAnnouncement.notif_type' => 1,
6704                    'TeacherAnnouncementsRead.id' => NULL,
6705                    'OR' => array(
6706                        array('FIND_IN_SET('.$type.', `TeacherAnnouncement.teacher_type`)'),
6707                        array('FIND_IN_SET('.$id.', `TeacherAnnouncement.teacher_id`)'),
6708                        array('FIND_IN_SET('.$type.', `TeacherAnnouncement.teacher_group_type`)'),
6709                        array('TeacherAnnouncement.target_teacher IN' => array(2, $home_flg)),
6710                        $query
6711                    ),    
6712                ),
6713                'joins' => array(
6714                    array(
6715                        'table' => 'teacher_announcements_read',
6716                        'alias' => 'TeacherAnnouncementsRead',
6717                        'type' => 'LEFT',
6718                        'conditions' => array(
6719                            'TeacherAnnouncementsRead.announcement_id = TeacherAnnouncement.id',
6720                            'TeacherAnnouncementsRead.teacher_id = ' . $id,
6721                        )
6722                    )
6723                ),
6724                'order' => 'TeacherAnnouncement.id DESC',
6725            ));
6726            $this->TeacherAnnouncement->closeDBReplica();
6727
6728            return json_encode($announcement);
6729        }
6730        return false;
6731    }
6732
6733    /**
6734     * @api {post} /teacher/api/getAnnouncementRead getAnnouncementRead()
6735     * @apiName getAnnouncementRead
6736     * @apiGroup API
6737     * @apiDescription Mark the announcement as read.
6738     * @apiSampleRequest off
6739     * 
6740     * @apiBody {String} id The ID of the announcement.
6741     * 
6742     * @apiErrorExample {bool} Success-Response:
6743     * true
6744     * 
6745      * @apiErrorExample {js} Used in: AngularJS
6746      * Location: "webroot/js/ng/app.js"
6747     */
6748    public function getAnnouncementRead() {
6749        $this->autoRender = false;
6750        if($this->request->is('post')){
6751            $post = $this->request->data;
6752            $announcementID = $post['id'];
6753            if(trim($announcementID) != ''){
6754                $data = array(
6755                        'announcement_id' => $announcementID,
6756                        'teacher_id' => $this->Auth->User('id')
6757                    );
6758                $this->TeacherAnnouncementsRead->create();
6759                $this->TeacherAnnouncementsRead->set($data);
6760                if ($this->TeacherAnnouncementsRead->save()) {
6761                    return true;
6762                } else {
6763                    return false;
6764                }
6765            } else {
6766                return false;
6767            }
6768        } else {
6769            return false;
6770        }
6771    }
6772
6773    /**
6774     * @api {get} /teacher/api/getLessonRequest getLessonRequest()
6775     * @apiName getLessonRequest
6776     * @apiGroup API
6777     * @apiDescription Get the lesson request for the teacher.
6778     * @apiSampleRequest off
6779     * 
6780     * @apiBody {String} userId The ID of the user.
6781     */
6782    
6783    public function getLessonRequest() {
6784        $this->autoRender = false;
6785        $data['success'] = false;
6786        $userId = isset($this->request->query['userId']) ? $this->request->query['userId'] : '';
6787        return $this->LessonRequest->getLessonRequest(array('userId' => $userId, 'viewingPage' => 'teacher'));;
6788    }
6789
6790    /**
6791     * @api {post} /teacher/api/translateText translateText()
6792     * @apiName translateText
6793     * @apiGroup API
6794     * @apiDescription Translate the text to the target language.
6795     * @apiSampleRequest off
6796     * 
6797     * @apiBody {String} text The text to be translated.
6798     * @apiBody {String} language The target language for translation.
6799     *
6800     * @apiErrorExample {json} Success-Response:
6801     * Returns myTools::translateText($text, $target_language)
6802     *
6803     * @apiErrorExample {js} Used in: Elements
6804     * Location: "view/Elements/chat_area_js.php"
6805     */
6806    /*
6807    * Translate teacher message from language to english language
6808    * youtube tutorial https://www.youtube.com/watch?v=j6Z3EQRvpq4
6809    * return @translated text
6810    */
6811    public function translateText() {
6812        $this->autoRender = false;
6813        if ($this->request->is('ajax')) {
6814            $text = isset($this->request->data['text']) ? $this->request->data['text']:"";
6815            $target_language = isset($this->request->data['language']) ? $this->request->data['language']:"";
6816
6817            return myTools::translateText($text, $target_language);
6818        }
6819    }
6820
6821    /**
6822     * @api {get} /teacher/api/checkTeacherEptSetting checkTeacherEptSetting()
6823     * @apiName checkTeacherEptSetting
6824     * @apiGroup API
6825     * @apiDescription Check if teacher has EPT setting.
6826     * @apiSampleRequest off
6827     * 
6828     * @apiSuccess {Boolean} showModal The status of the request.
6829     * 
6830     * @apiErrorExample {json} Success-Response:
6831     *     {
6832     *       "showModal": true,
6833     *     }
6834     *
6835     * @apiErrorExample {json} Error-Response:
6836     *     {
6837     *       "showModal": false,
6838     *     }
6839     * 
6840      * @apiErrorExample {js} Used in: AngularJS
6841      * Location: "webroot/js/ng/app.js"
6842
6843     * @apiErrorExample {js} Used in: Elements
6844     * Location: "view/Elements/home_based_expedite_payment_content.php"
6845     */
6846    public function checkTeacherEptSetting(){
6847        $this->autoRender = false;
6848        $response = array('showModal' => false);
6849        $check = $this->Session->read('EPT.teacher_seen_modal');
6850
6851        if(!$check){
6852            $settings = $this->Teacher->getTeacherEptSettings($this->Auth->user('id'));
6853            if($settings['ept_flg'] && !$settings['ept_status']){
6854                $response['showModal'] = true;
6855                $this->Session->write('EPT.teacher_seen_modal', true);
6856            }
6857        }
6858
6859        return json_encode($response);
6860    }
6861
6862    /**
6863     * @api {post} /teacher/api/lessonOnAirRequestLessonTime lessonOnAirRequestLessonTime()
6864     * @apiName lessonOnAirRequestLessonTime
6865     * @apiGroup API
6866     * @apiDescription Get lessonOnAir request lesson time.
6867     * @apiSampleRequest off
6868     * 
6869     * @apiSuccess {Boolean} success The status of the request.
6870     * @apiSuccess {Number} requestLessonTimeSec The request lesson time in seconds.
6871     * @apiSuccess {Number} requestLessonTimeMin The request lesson time in minutes.
6872     * @apiSuccess {Boolean} requestLessonVisible The request lesson visibility.    
6873     * @apiSuccess {String} textBookNameEng The English textbook name.
6874     * @apiSuccess {String} textBookNameJpn The Japanese textbook name.
6875     * @apiSuccess {String} current_textbook_element The current textbook element.
6876     * @apiSuccess {Number} chocotto_camp_lesson_flg The chocotto camp lesson flag.
6877     * 
6878     * @apiErrorExample {json} Success-Response:
6879     *     {
6880     *       "success": true,
6881     *       "requestLessonTimeSec": 1500,
6882     *       "requestLessonTimeMin": 25,
6883     *       "requestLessonVisible": true,
6884     *       "textBookNameEng": "English Textbook Name",
6885     *       "textBookNameJpn": "Japanese Textbook Name",
6886     *       "current_textbook_element": "[ Textbook or Course ] Category Name<br>[ Category ] Subcategory Name<br>[ Chapter ] Chapter Name<br>",
6887     *       "chocotto_camp_lesson_flg": 1
6888     *     }
6889     *
6890     * @apiErrorExample {json} Error-Response:
6891     *     {
6892     *       "success": false,
6893     *       "requestLessonTimeSec": 1500,
6894     *       "requestLessonTimeMin": 25,
6895     *       "requestLessonVisible": false,
6896     *       "textBookNameEng": null,
6897     *       "textBookNameJpn": null,
6898     *       "current_textbook_element": null,
6899     *       "chocotto_camp_lesson_flg": 0
6900     *     }
6901     * 
6902     * @apiErrorExample {js} Used in: Elements
6903     * Location: "view/Elements/chat_area_js.php"
6904     */
6905    /**
6906    * Get lessonOnAir request lesson time
6907    * No Params
6908    * @return mixed - json
6909    */
6910    public function lessonOnAirRequestLessonTime() {
6911        $this->autoRender = false;
6912        $dataResponse = array();
6913        $dataResponse['success'] = false;
6914        $dataResponse['requestLessonTimeSec'] = 1500;
6915        $dataResponse['requestLessonTimeMin'] = 25;
6916        $dataResponse['requestLessonVisible'] = false;
6917        $dataResponse['textBookNameEng'] = null;
6918        $dataResponse['textBookNameJpn'] = null;    
6919        $currentTextbookElement = null;    
6920
6921        $chatHash = isset($this->request->query['chat_hash']) ? $this->request->query['chat_hash'] : '';
6922        $userLocale = (isset($this->request->query['studentLessonLocalizeDir']) && $this->request->query['studentLessonLocalizeDir']) ? $this->request->query['studentLessonLocalizeDir'] : Configure::read('default.user_language');
6923
6924        $teacherId = $this->Auth->user('id');
6925        if (!$this->Auth->user('id')) {
6926            return json_encode($dataResponse);
6927        }
6928        $lessonTime = $this->LessonOnair->find('first', array(
6929            'fields' => array(
6930                'LessonOnair.requested_lesson_time',
6931                'LessonOnair.start_time',
6932                'LessonOnair.end_time',
6933                'LessonOnair.lesson_type',
6934                'LessonOnair.user_id',
6935                'LessonOnair.user_agent',
6936                'LessonOnair.connect_id',
6937                'LessonOnair.chocotto_camp_lesson_flg',
6938                'TextbookCategory.id',
6939                'TextbookCategory.name',
6940                'TextbookCategory.english_name',
6941                'TextbookSubCategory.name',
6942                'Textbook.id',
6943                'Textbook.name',
6944                'Textbook.name_eng',
6945                'Textbook.main_topic_id',
6946                'TextbookSubCategory.id',
6947                'TextbookSubCategory.english_name',
6948                'User.native_language2'
6949            ),
6950            'conditions'=> array(
6951                'LessonOnair.chat_hash' => $chatHash,
6952                'LessonOnair.teacher_id' => $teacherId,
6953                'LessonOnair.connect_id !=' => NULL
6954            ),
6955            'joins' => array(
6956                array(
6957                        'type' => 'LEFT',
6958                        'table' => 'textbook_connects',
6959                        'alias' => 'TextbookConnect',
6960                        'conditions' => 'LessonOnair.connect_id = TextbookConnect.id'
6961                    ),
6962                array(
6963                    'type' => 'LEFT',
6964                    'table' => 'textbook_categories',
6965                    'alias' => 'TextbookCategory',
6966                    'conditions' => 'TextbookConnect.category_id = TextbookCategory.id'
6967                ),
6968                array(
6969                    'type' => 'LEFT',
6970                    'table' => 'textbook_subcategories',
6971                    'alias' => 'TextbookSubCategory',
6972                    'conditions' => 'TextbookConnect.subcategory_id = TextbookSubCategory.id'
6973                ),
6974                array(
6975                    'type' => 'LEFT',
6976                    'table' => 'textbooks',
6977                    'alias' => 'Textbook',
6978                    'conditions' => 'Textbook.id = TextbookConnect.textbook_id'
6979                ),
6980                array(
6981                    'type' => 'LEFT',
6982                    'table' => 'users',
6983                    'alias' => 'User',
6984                    'conditions' => 'User.id = LessonOnair.user_id'
6985                )
6986
6987            ),
6988            'order' =>array('LessonOnair.id' => 'DESC'),
6989            'recursive' => -1
6990        ));
6991
6992        // ~ get teacher information
6993        $this->Teacher->openDBReplica();
6994        $teacherData = $this->Teacher->find('first', array(
6995            'fields' => ['avatar_id'],
6996            'conditions' => array('Teacher.id' => $teacherId),
6997            'recursive' => -1
6998        ));
6999        $this->Teacher->closeDBReplica();
7000        $teacherData = new TeacherTable($teacherData['Teacher']);
7001
7002        if (!$lessonTime['LessonOnair']['requested_lesson_time']) {
7003            return json_encode($dataResponse);
7004        }
7005
7006        $pcDeviceType = "PC";
7007        //get user device
7008        $studentDeviceType = myTools::getUsersDevice($lessonTime['LessonOnair']['user_agent']); 
7009        //for mobile lesson
7010        //if device type is not a PC use student language to get the global translation
7011        if ((isset($studentDeviceType) && $studentDeviceType) && $studentDeviceType != $pcDeviceType) {
7012            $userLocale = (isset($lessonTime['User']['native_language2']) && $lessonTime['User']['native_language2']) ? $lessonTime['User']['native_language2'] : 'en';
7013        }
7014
7015        $dataResponse['success'] = true;
7016        $dataResponse['requestLessonTimeSec'] = $lessonTime['LessonOnair']['requested_lesson_time'];
7017        $dataResponse['requestLessonTimeMin'] = ($dataResponse['requestLessonTimeSec']/60);
7018        $dataResponse['requestLessonTimeMinNotification'] = $dataResponse['requestLessonTimeMin'];
7019        if ($lessonTime['LessonOnair']['lesson_type'] == 1) {
7020            $dataResponse['requestLessonVisible'] = true;
7021
7022            //NJ-1446: set the actual seconds including 1min allowance
7023            $_requestedLessonSeconds = (int) $lessonTime['LessonOnair']['requested_lesson_time'] + 60;
7024
7025            //NJ-1446: fetch the difference  
7026            $_differenceLessonRequest = strtotime($lessonTime['LessonOnair']['end_time']) - strtotime($lessonTime['LessonOnair']['start_time']);
7027
7028            //NJ-1446 check if need to adjust the notified requested lesson time 
7029            if ($_requestedLessonSeconds > (int) $_differenceLessonRequest) {
7030                //adjust the requested time 
7031                $_adjustedRequestNotification = ($_differenceLessonRequest / 60);
7032                $_adjustedRequestNotification = (int) $_adjustedRequestNotification;
7033
7034                $dataResponse['requestLessonTimeMinNotification'] = $_adjustedRequestNotification;
7035            }
7036        }
7037
7038        $memcached = new myMemcached();
7039        $textbookNamesCached = $memcached->get(Configure::read('textbook_names_cache_key'));
7040        $order = (isset($textbookNamesCached[$lessonTime['LessonOnair']['connect_id']]['order']) && $textbookNamesCached[$lessonTime['LessonOnair']['connect_id']]['order']) ? $textbookNamesCached[$lessonTime['LessonOnair']['connect_id']]['order'] : '';
7041
7042
7043        // GET GLOBAL TRANSLATION OF TEXTBOOK, CATEGORY AND SUBCATEGORY DATA
7044        $glTextbook = GlobalTextbookTable::getGlobalData($lessonTime['Textbook']['id']);
7045        $glTextbookSubcategory = GlobalTextbookSubcategoryTable::getGlobalData($lessonTime['TextbookSubCategory']['id']);
7046        $glTextbookCategory = GlobalTextbookCategoryTable::getGlobalData($lessonTime['TextbookCategory']['id']);
7047
7048        $textBookChapterName = ($userLocale != Configure::read('default.user_language')) ? ( ($userLocale != 'en') ? ( (isset($glTextbook[$userLocale]['gl_name']) && $glTextbook[$userLocale]['gl_name']) ? $glTextbook[$userLocale]['gl_name'] : $lessonTime['Textbook']['name_eng'] ) : $lessonTime['Textbook']['name_eng'] ) : $lessonTime['Textbook']['name'];
7049        $textBookChapterNameEng = ( isset($lessonTime['Textbook']['name_eng']) && $lessonTime['Textbook']['name_eng'] ) ? $lessonTime['Textbook']['name_eng'] : ( (isset($glTextbook['en']['gl_name']) && $glTextbook['en']['gl_name']) ? $glTextbook['en']['gl_name'] : '' );
7050
7051        $textbookClassName = ($userLocale != Configure::read('default.user_language')) ? ( ($userLocale != 'en') ? ( (isset($glTextbookSubcategory[$userLocale]['gl_name']) && $glTextbookSubcategory[$userLocale]['gl_name']) ? $glTextbookSubcategory[$userLocale]['gl_name'] : $lessonTime['TextbookSubCategory']['english_name'] ) : $lessonTime['TextbookSubCategory']['english_name'] ) : $lessonTime['TextbookSubCategory']['name'];
7052        $textbookClassNameEng = (isset($lessonTime['TextbookSubCategory']['english_name']) && $lessonTime['TextbookSubCategory']['english_name']) ? $lessonTime['TextbookSubCategory']['english_name'] : ( (isset($glTextbookSubcategory['en']['gl_name']) && $glTextbookSubcategory['en']['gl_name']) ? $glTextbookSubcategory['en']['gl_name'] : '' );
7053
7054        $textbookCategoryName = ($userLocale != Configure::read('default.user_language')) ? ( ($userLocale != 'en') ? ( (isset($glTextbookCategory[$userLocale]['gl_name']) && $glTextbookCategory[$userLocale]['gl_name']) ? $glTextbookCategory[$userLocale]['gl_name'] : $lessonTime['TextbookCategory']['english_name'] ) : $lessonTime['TextbookCategory']['english_name'] ) : $lessonTime['TextbookCategory']['name'];
7055        $textbookCategoryNameEng = (isset($lessonTime['TextbookCategory']['english_name']) && $lessonTime['TextbookCategory']['english_name']) ? $lessonTime['TextbookCategory']['english_name'] : ( (isset($glTextbookCategory['en']['gl_name']) && $glTextbookCategory['en']['gl_name']) ? $glTextbookCategory['en']['gl_name'] : '' );
7056
7057        $dataResponse['textBookName'] = ($textbookClassName || $textBookChapterName) ? $textbookClassName .' - '. $order . $textBookChapterName : null;
7058        $dataResponse['textBookNameEng'] = ($textbookClassNameEng || $textBookChapterNameEng) ? $textbookClassNameEng .' - ' . $textBookChapterNameEng : null;
7059
7060        $dataResponse['category_name'] = $textbookCategoryName;
7061        $dataResponse['category_name_eng'] = $textbookCategoryNameEng;
7062
7063        $categoryNameUse = empty($textbookCategoryNameEng) ? $textbookCategoryName : $textbookCategoryNameEng ;
7064        $subCategoryNameUse = empty($textbookClassNameEng) ? $textbookClassName : $textbookClassNameEng ;
7065        $textbookNameUse = empty($textBookChapterNameEng) ? $textBookChapterName : $textBookChapterNameEng ;
7066        // check for main topic
7067        if ( isset($lessonTime['Textbook']['main_topic_id']) && $lessonTime['Textbook']['main_topic_id'] ) {
7068            $textbookMainTopicNameEn = ClassRegistry::init('Textbook')->getTextbookMainTopicName($lessonTime['Textbook']['id'],Configure::read('default.teacher_timezone_id'));
7069            if ($textbookMainTopicNameEn) { 
7070                $subCategoryNameUse = $textbookMainTopicNameEn;
7071            } else {
7072                $langId = ClassRegistry::init('CountryCode')->getUserLanguageId($userLocale);
7073                $textbookMainTopicName = ClassRegistry::init('Textbook')->getTextbookMainTopicName($lessonTime['Textbook']['id'],$langId);
7074                if ($textbookMainTopicName) { 
7075                    $subCategoryNameUse = $textbookMainTopicNameEn; 
7076                }
7077            }
7078        }
7079        if ( $categoryNameUse && $subCategoryNameUse && $textbookNameUse ) {
7080
7081            $currentTextbookElement .= '[ Textbook or Course ] ' . $categoryNameUse . '<br>';
7082            $currentTextbookElement .= '[ Category ] ' . $subCategoryNameUse . '<br>';
7083            $currentTextbookElement .= '[ Chapter ] ' . $order . $textbookNameUse . '<br>';
7084
7085            $dataResponse['current_textbook_element'] = $currentTextbookElement;
7086        }
7087
7088        // - NJ-27262, Chocotto Lesson Flg
7089        $dataResponse['chocotto_camp_lesson_flg'] = $lessonTime['LessonOnair']['chocotto_camp_lesson_flg'];
7090
7091        return json_encode($dataResponse);
7092    }
7093    
7094    /**
7095     * @api {post} /teacher/api/getDateStatus getDateStatus()
7096     * @apiName getDateStatus
7097     * @apiGroup API
7098     * @apiDescription Get the information about the certain slot.
7099     * @apiSampleRequest off
7100     * 
7101     * @apiBody {String} teacherId The ID of the teacher.
7102     * @apiBody {String} dateTime The date and time.
7103     * 
7104     * @apiSuccess {Number} reservedStudent The number of reserved students.
7105     * @apiSuccess {Number} lessonRequentCount The number of lesson requests.
7106     * @apiSuccess {Number} mealBreakCount The number of meal breaks.
7107     * @apiSuccess {Number} slotCount The number of slots.
7108     * @apiSuccess {String} currentServerTime The current server time.
7109     * 
7110     * 
7111     * @apiErrorExample {json} Success-Response:
7112     *     {
7113     *       "reservedStudent": 5,
7114     *       "lessonRequentCount": 3,
7115     *       "mealBreakCount": 2,
7116     *       "slotCount": 4,
7117     *       "currentServerTime": "2023-10-01"
7118     *     }
7119     *
7120     * @apiErrorExample {json} Error-Response:
7121     *     {
7122     *       "reservedStudent": 0,
7123     *       "lessonRequentCount": 0,
7124     *       "mealBreakCount": 0,
7125     *       "slotCount": 0,
7126     *       "currentServerTime": "2023-10-01"
7127     *     }
7128     * 
7129      * @apiErrorExample {js} Used in: AngularJS
7130     * Location: "webroot/js/ng/controller/schedule.js"
7131     */
7132    /**
7133    * NC6202 - get the information about the certain slot
7134    * @param
7135    * @return
7136    */
7137    public function getDateStatus() {
7138        $this->autoRender = false;
7139        $this->layout = false;
7140
7141        $params = $this->request['data'];
7142        $teacherId = $params['teacherId'];
7143        $dateTime = date('Y-m-d H:i:s', strtotime($params['dateTime']));
7144
7145        // - if has no timezone information
7146        $this->refreshTeacherTZ();
7147
7148        //count student reserved
7149        $reserved = $this->LessonSchedule->isReservedFromTeacherIdAndReserveTime($teacherId, $dateTime, null);
7150        $resData['reservedStudent'] = $reserved;
7151
7152        //-- count lesson request
7153        $statusType = array(
7154            Configure::read('lesson_schedule.status.lesson_request'),
7155            Configure::read('lesson_schedule.status.request_confirmation')
7156        );
7157        $resData['lessonRequentCount'] = $this->LessonSchedule->isReservedFromTeacherIdAndReserveTime($teacherId, $dateTime, null, $statusType);
7158
7159        //check for mealbreak on teacher
7160        $limitPerDay = $this->ShiftWorkMealBreak->countTeacherMealbreak($teacherId, $dateTime);
7161        $resData['mealBreakCount'] = $limitPerDay;
7162
7163        //counts teacher slot that are reserved for lesson
7164        $slotCount = $this->ShiftWorkOn->countTeacherSlotOn($teacherId, $dateTime, $this->timeDiff);
7165        $resData['slotCount'] = $slotCount;
7166
7167        $resData['currentServerTime'] = date('Y-m-d');
7168        return json_encode($resData);
7169    }
7170        
7171    /**
7172     * @api {post} /teacher/api/checkFRSendSlack checkFRSendSlack()
7173     * @apiName checkFRSendSlack
7174     * @apiGroup API
7175     * @apiDescription Check if the face recognition should send a slack message.
7176     * @apiSampleRequest off
7177     * 
7178     * @apiBody {String} chatHash The chat hash.
7179     *
7180     * @apiSuccess {Boolean} status The status of the request.
7181     * @apiSuccess {String} msg The message.
7182     * 
7183     * @apiErrorExample {json} Success-Response:
7184     *     {
7185     *       "status": true,
7186     *       "msg": "send message!"
7187     *     }
7188     *
7189     * @apiErrorExample {json} Error-Response:
7190     *     {
7191     *       "status": false,
7192     *       "msg": "nothing to send"
7193     *     }
7194     * 
7195     * @apiErrorExample {js} Used in: Elements
7196     * Location: "view/Elements/chat_area_js.php"
7197     */
7198    public function checkFRSendSlack($chatHash = '') {
7199        $this->autoRender = false;
7200        $response = array('status' => false, 'msg' => 'nothing to send');
7201
7202        $hasRecord = $this->FaceRecognition->find('first', array(
7203            'fields' => array(
7204                'FaceRecognition.id',
7205                'FaceRecognition.image',
7206                'LessonTrackLog.lesson_number',
7207                'FaceRegistration.image',
7208                'User.id',
7209                'User.status',
7210                'User.corporate_id',
7211                'User.charge_flg',
7212                'User.fail_flg',
7213                'User.double_check_flg',
7214                'User.double_check_flg',
7215                'User.parent_id',
7216                'User.payment_plan_id',
7217                'User.hash16',
7218                'User.complimentary_code',
7219                'User.corporate_type',
7220                'User.nickname',
7221                'User.currency_code',
7222                'User.native_language2',
7223                'User.country_code',
7224                '(SELECT COUNT(*) FROM face_recognitions as recog WHERE recog.chat_hash = "'.$chatHash.'" AND recog.mismatch_flg = 0) as hasMatch',
7225                'User.studysapuri_id'
7226            ),
7227            'conditions' => array(
7228                'FaceRecognition.chat_hash' => $chatHash,
7229                'FaceRecognition.mismatch_flg !=' => 2
7230            ),
7231            'joins' => array(
7232                array(
7233                    'type' => 'LEFT',
7234                    'table' => 'lesson_track_logs',
7235                    'alias' => 'LessonTrackLog',
7236                    'conditions' => 'LessonTrackLog.chat_hash = FaceRecognition.chat_hash'
7237                ),
7238                array(
7239                    'type' => 'LEFT',
7240                    'table' => 'users',
7241                    'alias' => 'User',
7242                    'conditions' => 'User.id = FaceRecognition.user_id'
7243                ),
7244                array(
7245                    'type' => 'LEFT',
7246                    'table' => 'face_registrations',
7247                    'alias' => 'FaceRegistration',
7248                    'conditions' => 'FaceRegistration.user_id = FaceRecognition.user_id AND FaceRegistration.id = FaceRecognition.registered_image_id AND FaceRegistration.teacher_flg = 0'
7249                )
7250            ),
7251            'order' => 'FaceRecognition.id DESC'
7252        ));
7253        $this->log("[FR_LESSONFINISH] " . json_encode($hasRecord), 'debug');
7254
7255        if ($hasRecord && isset($hasRecord[0]['hasMatch']) && !$hasRecord[0]['hasMatch']) {
7256            $user = $hasRecord['User'];
7257            $userId = $user['id'];
7258            $faceRecog = $hasRecord['FaceRecognition'];
7259            $faceReg = $hasRecord['FaceRegistration'];
7260            $lessonLog = $hasRecord['LessonTrackLog'];
7261
7262            $membershipType = UserTable::getEngMembershipTypeData();
7263            $userObj = new UserTable($user);
7264            $userMembType = $userObj->getMembershipTypeIndex();
7265            $suddenLesson = $this->User->getSuddenLessonCount($user['id']);
7266            $zeroStudentDiscountOptionInUse = 1;
7267            $zeroStudentStatus = "OFF";
7268
7269            // connect to aws
7270            $s3Client = new AwsFileServer();         
7271            // get filename 
7272            $faceRecogFileName = basename($faceRecog['image']);                    
7273            $faceRegFileName = basename($faceReg['image']);
7274            $discountOptionTerm = $this->UserDiscountOptionsTerm->getTerm([
7275                'user_id' =>  $userId,
7276                'discount_option_id' => Configure::read('discount_option.zero_student.plan_id'),
7277                'status' => 1
7278            ]);
7279            // if zero student discount option user
7280            if ($discountOptionTerm && $discountOptionTerm['discount_option_id'] == Configure::read('discount_option.zero_student.plan_id')) {
7281                $zeroStudentDiscountOptionInUse = 1;
7282                $zeroStudentStatus = "ON";
7283            }
7284
7285            #6796 set default last 30 day from today
7286            $startDateFromToday = strtotime('-29 days', time());
7287            $defaultDate = date('M d, Y (D)', $startDateFromToday) . ' - ' . date('M d, Y (D)');
7288
7289            $text = "@group-cs \n";
7290            $text .= "```種別: 顔認証不一致通知" . "\n";
7291            $text .= "URL: " . myTools::getUrl() . "/admin/face-recognition?recognition_id={$faceRecog['id']}&lesson_id={$lessonLog['lesson_number']} \n";
7292            $text .= "会員名:{$user['nickname']} \n";
7293            $text .= "会員ID:" . "<" . myTools::getBaseUrl() . "/admin/user-manage/member/" . $userId . "|" . $userId . ">" . " \n";
7294            $text .= "ゼロ学割:" . $zeroStudentStatus . " \n";
7295            if($zeroStudentDiscountOptionInUse) {
7296                $text .= "ゼロ学割承認画面URL: " . myTools::getUrl() . "admin/student-discount-approval-screen?userId={$userId}&nickname={$user['nickname']} \n";
7297            }
7298            $text .= "通貨 : {$user['currency_code']} \n";
7299            $text .= "言語: {$user['native_language2']} \n";
7300            $text .= "直近30日の今すぐレッスン受講数:{$suddenLesson}回 \n";
7301            #6796 add lessionUnitPrice,link message-manage,link lesson-history
7302            $text .= "レッスン単価: {$this->lessionUnitPrice($user)} \n";
7303            $text .= "<" . myTools::getBaseUrl() . "/admin/message-manage?user_id={$userId}&display_flg=all&start_time={$defaultDate}&sent_time={$defaultDate}" . "|" . "メッセージ履歴" . ">" . " \n";
7304            $text .= "<" . myTools::getBaseUrl() . "/admin/lesson-history?user_id={$userId}&date={$defaultDate}" . "|" . "レッスン履歴" . ">" . " \n";
7305            $text .= "会員種別:{$membershipType[$userMembType]} \n";
7306            $text .= "SMS国コード:{$user['country_code']} \n";
7307
7308            // NC-10020: show family plan ids
7309            // find Family account by $parentId
7310
7311            $userIdIsParentId = false;
7312
7313            // incase of user is child and have parent_id;
7314            if ($user['parent_id']) {
7315                $parentId = $user['parent_id'];
7316            } else {
7317                // user dont have parent_id, user may be parent
7318                $parentId = $userId;
7319                $userIdIsParentId = true;
7320            }
7321
7322            // get all child and exclude current user id
7323            $allChild = $this->FamilyPlanList->find('all', array(
7324                    'fields' => array('FamilyPlanList.family_id'),
7325                    'conditions' => array(
7326                        'FamilyPlanList.parent_id' => $parentId,
7327                        'FamilyPlanList.approve_flg' => 1
7328                    ),
7329                    'order' => array('FamilyPlanList.created DESC')
7330                ));
7331
7332            // child array is exists, process send linked parent and child id to slack
7333            if (count($allChild) > 0) {
7334                $text .= "関連する親/子ID一覧:\n";
7335
7336                // check whether current user is parent or not
7337                // if user is parent, dont show parent Id (redundant information, we have show userId above)
7338                if (!$userIdIsParentId) {
7339                    // user is not parent id, show parent_id
7340                    $text .= "親ID:" . "<" . myTools::getBaseUrl() . "/admin/user-manage/member/" . $parentId . "|" . $parentId . ">" . " \n";
7341                };
7342
7343                foreach ($allChild as $child) {
7344                    $chidlId = $child['FamilyPlanList']['family_id'];
7345                    if($chidlId !== $userId) {
7346                        $text .= "子ID:" . "<" . myTools::getBaseUrl() . "/admin/user-manage/member/" . $chidlId  . "|" . $chidlId  . ">" . " \n";
7347                    }
7348                }
7349            }
7350
7351            $text .= "```\n";
7352
7353            $blocks = array(
7354                array(
7355                    "type"=> "section",
7356                    "text" => array(
7357                        "type"=> "mrkdwn",
7358                        "text"=> $text
7359                    )
7360                ),
7361                array(
7362                    "type" => "divider"
7363                ),
7364                array(
7365                    "type"=> "section",
7366                    "text" => array(
7367                        "type"=> "mrkdwn",
7368                        "text"=> "元顔画像:"
7369                    ),
7370                    "accessory" => array(
7371                        "type" => "image",
7372                        "image_url" => $s3Client->getPreSignedUrl($faceRegFileName, false),
7373                        "alt_text" => "元顔画像"
7374                    )
7375                ),
7376                array(
7377                    "type" => "section",
7378                    "text" => array(
7379                        "type" => "mrkdwn",
7380                        "text"=> "本レッスンでの顔画像:"
7381                    ),
7382                    "accessory" => array(
7383                        "type" => "image",
7384                        "image_url" => $s3Client->getPreSignedUrl($faceRecogFileName, false),
7385                        "alt_text" => "本レッスンでの顔画像"
7386                    )
7387                )
7388
7389            );
7390
7391            $mySlack = new mySlack();
7392            $mySlack->channel = myTools::checkChannel("#nc-face-recognition", "#fdc-test-channel");
7393            $mySlack->link_names = true;
7394            $mySlack->blocks = $blocks;
7395            $mySlack->username = "Face Recognition";
7396            $mySlack->sendSlack();
7397
7398            $this->log("[FR_SENDSLACK] lesson_id=" . $lessonLog['lesson_number'], 'debug');
7399            $response = array('status' => true, 'msg' => 'send message!');
7400        }
7401
7402        return json_encode($response);
7403    }
7404
7405    /**
7406    * #6796 Caculate lessionUnitPrice
7407    * @return number $lessionUnitPrice
7408    */
7409    private function lessionUnitPrice($user) {
7410        $lessionUnitPrice = "";
7411        $currencyOptions = CurrencyTable::getCurrencyList();
7412        $getsumAmount = $this->Payment->useReplica()->find('first',array(
7413            'fields' => array(
7414                'sum(Payment.amount) as sum_amount'),
7415            'conditions' => array(
7416                'Payment.user_id' => $user['id'],
7417                'Payment.param2' => '')
7418        ));
7419        $sumAmount = isset($getsumAmount[0]['sum_amount']) ? $getsumAmount[0]['sum_amount'] : 0;
7420
7421        # Number of lesson
7422        $lessonNumber = $this->LessonOnairsLog->useReplica()->find('count',array(
7423            'conditions' => array(
7424                'LessonOnairsLog.user_id' => $user['id'],
7425            )
7426        ));
7427        if($sumAmount != 0 && $lessonNumber != 0){
7428            $lessionUnitPrice = (isset($currencyOptions[$user['currency_code']]) ? $currencyOptions[$user['currency_code']] : '') . " " . floor($sumAmount/$lessonNumber) . " / Lesson";
7429        }
7430        return $lessionUnitPrice;
7431    }
7432
7433    /**
7434     * @api {post} /teacher/api/studentYearlySpeakingTest studentYearlySpeakingTest()
7435     * @apiName studentYearlySpeakingTest
7436     * @apiGroup API
7437     * @apiDescription Get student speaking test data for the whole year.
7438     * @apiSampleRequest off
7439     * 
7440     * @apiBody {String} userId The ID of the user.
7441     * @apiBody {String} queryYear The year.
7442     * @apiBody {String} testType The type of the test.
7443     * 
7444     * @apiSuccess {Array} data The data (`UsersMonthlyDiagnosis->studentYearlySpeakingTestSummary($userId, $queryYear, $testType)`).
7445     * 
7446     * @apiErrorExample {json} Success-Response:
7447     *     {
7448     *       "data": [...]
7449     *     }
7450     *
7451      * @apiErrorExample {js} Used in: AngularJS
7452      * Location: "webroot/js/ng/controller/student_info.js"
7453     */
7454     /**
7455    * GET student speaking test data for the whole year
7456    */
7457    public function studentYearlySpeakingTest () {
7458        $this->autoRender = false;
7459        $this->layout = false;
7460        $studentYearlySpeakingTest = array();
7461        $params = isset($this->request->data) ? $this->request->data : array();
7462        $userId = (isset($params['userId']) && $params['userId']) ? $params['userId'] : null;
7463        $queryYear = (isset($params['queryYear']) && $params['queryYear']) ? $params['queryYear'] : null;
7464        $testType = (isset($params['testType']) && $params['testType']) ? $params['testType'] : 0;
7465        if (!$userId || !$queryYear) {
7466            return json_encode($studentYearlySpeakingTest);
7467        }
7468        $UsersMonthlyDiagnosis = ClassRegistry::init('UsersChivoxMonthlyDiagnosis');
7469        $studentYearlySpeakingTest = $UsersMonthlyDiagnosis->studentYearlySpeakingTestSummary($userId, $queryYear, $testType);
7470        return json_encode($studentYearlySpeakingTest);
7471    }
7472
7473    /**
7474     * @api {get} /teacher/api/uploadRecordedFile uploadRecordedFile()
7475     * @apiName uploadRecordedFile
7476     * @apiGroup API
7477     * @apiDescription Save audio and get equivalent text from Google API.
7478     * @apiSampleRequest off
7479     * 
7480     * @apiBody {String} connect_id The ID of the connect.
7481     * 
7482     * @apiSuccess {Boolean} success The status of the request.
7483     * @apiSuccess {String} convertedSpeech The converted speech.
7484     * @apiSuccess {String} audio_url The audio URL.
7485     * @apiSuccess {String} textbook_name The textbook name.
7486     * 
7487     * @apiErrorExample {json} Success-Response:
7488     *     {
7489     *       "success": true,
7490     *       "convertedSpeech": "Converted text",
7491     *       "audio_url": "url",
7492     *       "textbook_name": "Category Subcategory : Textbook"
7493     *     }
7494     *
7495      * @apiErrorExample {js} Used in: JS
7496      * Location: "webroot/js/custom_sound-recorder.js"
7497     */
7498    /**
7499    * Save audio and get equivalent text from Google API
7500    * @return mixed - json $response
7501    */
7502   public function uploadRecordedFile() {
7503        $this->autoRender = false;
7504
7505        $response = array(
7506            'success' => false,
7507            'convertedSpeech' => '{"jpn" : "〔エラー〕:音声認識に問題があります。ヘッドセットあるいはイヤホンの着用/インターネット環境のご確認をお願いします","eng" : "Error : There is a problem with voice recognition. Please wear a headset or earphones and check your internet environment"}'
7508        );
7509
7510        // - check if request has a valid file
7511        if ((!isset($_FILES['audio_wav']['size']) || !$_FILES['audio_wav']['size']) || (!isset($_FILES['audio_mp3']['size']) || !$_FILES['audio_mp3']['size'])) {
7512            return json_encode($response);
7513        }
7514        
7515        $connectId                 = !empty($this->request->data['connect_id']) ? $this->request->data['connect_id'] : null;
7516        $storedRecordingFlag     = !empty($this->request->data['store_audio_record']) ? true : false;
7517        $userId                    = !empty($this->request->data['user_id']) ? $this->request->data['user_id'] : null;
7518        $questionName            = !empty($this->request->data['question_name']) ? $this->request->data['question_name'] : null;
7519        $questionTitle            = !empty($this->request->data['question_title']) ? $this->request->data['question_title'] : null;
7520
7521        //-- user current language
7522        $userLanguage = $this->User->fetchUserCurrentLanguage(array(
7523            'user_id' => $userId
7524        ));
7525
7526        // - override question name
7527        if (!empty($questionTitle)) {
7528            $questionName = $questionTitle;
7529        }
7530
7531        // - Get audio temporary folder
7532        $audioContainer = ROOT . '/instructor/webroot/sound';
7533        if (!is_dir($audioContainer)) {
7534            mkdir($audioContainer);
7535        }
7536
7537        // - move the file from temp name to local folder using $output name
7538        $wavInput = $_FILES['audio_wav']['tmp_name'];
7539        $mp3Input = $_FILES['audio_mp3']['tmp_name'];
7540
7541        $wavOutput = $audioContainer . '/' . $_FILES['audio_wav']['name'] . '.wav';
7542        $mp3Output = $audioContainer . '/' . $_FILES['audio_mp3']['name'] . '.mp3';
7543
7544        // - delete audio file if already exist
7545        if (file_exists($wavOutput)) {
7546            unlink($wavOutput);
7547        }
7548        if (file_exists($mp3Output)) {
7549            unlink($mp3Output);
7550        }
7551        
7552        // - upload file
7553        $moveFileStatusWav = move_uploaded_file($wavInput, $wavOutput);
7554        $moveFileStatusMp3 = move_uploaded_file($mp3Input, $mp3Output);
7555        $fileUrl = "";
7556
7557        //-- Move audio recordings to s3 
7558        $this->loadModel('FileStorage');
7559        if ($moveFileStatusWav && $moveFileStatusMp3 && !empty($storedRecordingFlag)) {
7560            // - upload file to s3
7561            $wavFilename = time() . uniqid() . '_wav_' . basename($wavOutput);
7562            $mp3Filename = time() . uniqid() . '_mp3_' . basename($mp3Output);
7563
7564            $wavResult = $this->FileStorage->uploadFile([
7565                'uploader_id'  => $this->Auth->user('id'),
7566                'uploader_type' => 34,
7567                'source'       => $wavOutput,
7568                'key'          => $wavFilename,
7569                'file'         => $_FILES['audio_wav'],
7570                'delete_image' => true
7571            ]);
7572            
7573            $mp3Result = $this->FileStorage->uploadFile([
7574                'uploader_id'  => $this->Auth->user('id'),
7575                'uploader_type' => 34,
7576                'source'       => $mp3Output,
7577                'key'          => $mp3Filename,
7578                'file'         => $_FILES['audio_mp3'],
7579                'delete_image' => true
7580            ]);
7581            $fileUrl = $mp3Result['FileStorage']['url'];
7582
7583            // - get audio response
7584            $response['audio_url'] = str_replace('https://', '', $fileUrl);
7585        }
7586
7587        // NC-9388: START GOOGLE API SPEECH TO TEXT PR0CESS
7588        if (isset($wavResult['FileStorage']['url'])) {
7589            App::uses('myRedis', 'Lib');
7590
7591            // - get speech
7592            $responseGoogle = myTools::performGooglSpeechToText([
7593                'audio_url' => $wavResult['FileStorage']['url'],
7594                'user_language' => $userLanguage
7595            ]);
7596            
7597            // - check if success
7598            if ($responseGoogle['success']) {
7599                $response['success'] = true;
7600                $response['convertedSpeech'] = $responseGoogle['convertedSpeech'] ?? '';
7601
7602                if (!empty($responseGoogle['totalTime']) && is_array($responseGoogle['totalTime'])) {
7603                    //get word timestamps
7604                    $sum = 0;
7605                    foreach ($responseGoogle['totalTime'] as $key => $value) {
7606                        foreach ($value as $key2 => $value2) {
7607                            //get each word start and end time
7608                            $startTime = floatval($value2['startTime']);
7609                            $endTime = floatval($value2['endTime']);
7610                            // sum the difference of start and end time
7611                            $sum += (float)($endTime -  $startTime);
7612                        }
7613                    }
7614
7615                    //save speech to text logs
7616                    $logParams = array(
7617                        'service' => 2,
7618                        'type' => 3,
7619                        'controller' => static::class,
7620                        'method' => __METHOD__,
7621                        'url' => $this->request->here(),
7622                        'value'    => number_format($sum, 2, '.', '')
7623                    );
7624                    $googleLogs = ClassRegistry::init('GoogleTranslateLog');
7625                    $result = $googleLogs->saveLog($logParams);
7626                    //check if logs saved
7627                    if (!$result) {
7628                        $this->log('error', __METHOD__ . " -- [Google Translate] Unable to save logs. ");
7629                    }
7630                }
7631            }
7632
7633            $response['textbook_name'] = '';            
7634
7635            $textbookName = $this->TextbookConnect->getTextbookName(array(
7636                'connect_id' => $connectId,
7637                'native_language' => $userLanguage,
7638                'translate_other_lang' => true
7639            ));
7640
7641            $response['textbook_name'] .= !empty($textbookName['textbook_category_name']) ? $textbookName['textbook_category_name'] : '';
7642            $response['textbook_name'] .= !empty($textbookName['textbook_subcategory_name']) ? ' ' . $textbookName['textbook_subcategory_name'] : '';
7643            $response['textbook_name'] .= !empty($textbookName['textbook_name']) ? ' : ' . $textbookName['textbook_name'] : '';
7644            $response['success'] = true;
7645
7646            // instantiate view
7647            $view = new View($this, false);
7648            $view->layout = false;
7649
7650            $viewData = array(
7651                'convertedSpeech' => $response['convertedSpeech'] ?? '',
7652                'isSpeechRecording' => 1,
7653                'question_name' => !empty($questionName) ? $questionName : $textbookName['textbook_name'],
7654                'audio_url' => $fileUrl
7655            ); 
7656
7657            $resultViewStudent = $view->element('student_chat_class_chivox', $viewData);
7658            $viewData['question_name'] = !empty($questionName) ? $questionName : $textbookName['textbook_name_eng'];
7659            $resultViewTeacher = $view->element('teacher_chat_class_chivox', $viewData);
7660
7661            $response['resultViewStudent'] = $resultViewStudent;
7662            $response['resultViewTeacher'] = $resultViewTeacher;
7663        }
7664        // NC-9388: END GOOGLE API SPEECH TO TEXT PR0CESS
7665
7666        // - delete audio file if it exist
7667        if (file_exists($wavOutput)) {
7668            unlink($wavOutput);
7669        }
7670        if (file_exists($mp3Output)) {
7671            unlink($mp3Output);
7672        }
7673        
7674        // - return json encode
7675        return json_encode($response);
7676    }
7677
7678    /**
7679     * @api {post} /teacher/api/uploadRecordedFileChivox uploadRecordedFileChivox()
7680     * @apiName uploadRecordedFileChivox
7681     * @apiGroup API
7682     * @apiDescription Save audio to aws, use to request chivox ai.
7683     * @apiSampleRequest off
7684     * 
7685     * @apiBody {String} chivox_pc The chivox pc.
7686     * @apiBody {String} refText The reference text.
7687     * @apiBody {String} sampleRate The sample rate.
7688     * @apiBody {String} chivox_type The chivox type.
7689     * @apiBody {String} keyword The keyword.
7690     * @apiBody {String} keyletterlocation The key letter location.
7691     * @apiBody {String} userID The ID of the user.
7692     * @apiBody {String} textbookCategory The textbook category.
7693     * @apiBody {String} kernel The kernel.
7694     * @apiBody {String} store_audio_record The store audio record.
7695     * @apiBody {String} connect_id The ID of the connect.
7696     * @apiBody {String} studentLessonLocalizeDir The student lesson localize directory.
7697     * 
7698     * @apiSuccess {Boolean} success The status of the request.
7699     * @apiSuccess {String} resultView The result view.
7700     * @apiSuccess {String} audio_url The audio URL.
7701     * @apiSuccess {String} textbook_name The textbook name.
7702     * 
7703     * @apiErrorExample {json} Success-Response:
7704     *     {
7705     *       "success": true,
7706     *       "resultView": "HTML content",
7707     *       "audio_url": "url",
7708     *       "textbook_name": "Category Subcategory : Textbook"
7709     *     }
7710     *
7711     * @apiErrorExample {json} Error-Response:
7712     *     {
7713     *       "success": false,
7714     *       "error": "Chivox Error!"
7715     *     }
7716     * 
7717      * @apiErrorExample {js} Used in: JS
7718      * Location: "webroot/js/custom_sound-recorder.js"
7719     */
7720    /**
7721    * Save audio to aws, use to request chivox ai
7722    * @return mixed  - json $response
7723    */
7724    public function uploadRecordedFileChivox () {
7725        $this->autoRender = false;
7726        $response['success'] = false;
7727
7728        // - check if request has a valid file
7729        if (
7730            (!isset($_FILES['audio_wav']['size']) || !$_FILES['audio_wav']['size'])
7731            || (!isset($_FILES['audio_mp3']['size']) || !$_FILES['audio_mp3']['size']) 
7732            || empty($this->request->data['refText'])
7733        ) {
7734            return json_encode($response);
7735        }
7736
7737        $qvalue_mp3 = $_FILES['audio_mp3'];
7738        $qvalue_wav = $_FILES['audio_wav'];
7739        $chivox_pc = isset($this->request->data['chivox_pc']) ? $this->request->data['chivox_pc'] : 0;
7740        $refText = isset($this->request->data['refText']) ? $this->request->data['refText'] : "";
7741        $chivoxSampleRate = Configure::read('chivox.sampleRate');
7742        $sampleRate = isset($this->request->data['sampleRate']) && $this->request->data['sampleRate'] <= $chivoxSampleRate ? $this->request->data['sampleRate'] : $chivoxSampleRate;
7743        $chivox_type = isset($this->request->data['chivox_type']) ? $this->request->data['chivox_type'] : 1;
7744        $keyword = isset($this->request->data['keyword']) ? $this->request->data['keyword'] : "";
7745        $keyletterlocation = isset($this->request->data['keyletterlocation']) ? $this->request->data['keyletterlocation'] : 1;
7746        $userID = isset($this->request->data['userID']) ? $this->request->data['userID'] : 0;
7747        $textbookCategory = isset($this->request->data['textbookCategory']) ? $this->request->data['textbookCategory'] : null;
7748        $kernel = isset($this->request->data['kernel']) ? $this->request->data['kernel'] : null;
7749        $storedRecordingFlag = !empty($this->request->data['store_audio_record']) ? true : false;
7750        $connectId = !empty($this->request->data['connect_id']) ?$this->request->data['connect_id'] : 0;
7751        $studentLessonLocalizeDir = isset($this->request->data['studentLessonLocalizeDir']) && !empty($this->request->data['studentLessonLocalizeDir']) ? $this->request->data['studentLessonLocalizeDir'] : null ;
7752        $questionName = !empty($this->request->data['question_name']) ? $this->request->data['question_name'] : "";
7753        $addParams = isset($this->request->data['addParams']) ? json_decode($this->request->data['addParams'], true) : null;
7754        $labelTranslations = $addParams['label_translations'] ?? [];
7755        $chatHash = !empty($this->request->data['chat_hash']) ? $this->request->data['chat_hash'] : "";
7756        $accent = isset($this->request->data['accent']) ? $this->request->data['accent'] : 3; // default is 3
7757        $recitation = isset($this->request->data['recitation']) ? $this->request->data['recitation'] : null;
7758
7759        // - Get audio temporary folder
7760        $audioContainer = ROOT . '/user/webroot/files';
7761        if (!is_dir($audioContainer)) {
7762            mkdir($audioContainer);
7763        }
7764
7765        //upload to amazon server
7766        $filename_mp3 = uniqid() . "_" . $_FILES['audio_mp3']['name'] . '.mp3';
7767        $filename_wav = uniqid() . "_" . $_FILES['audio_wav']['name'] . '.wav';
7768        //temporary location
7769        $tempLocation_mp3 = ROOT . '/user/webroot/files/'. $filename_mp3;
7770        $tempLocation_wav = ROOT . '/user/webroot/files/'. $filename_wav;
7771        // - delete audio file if already exist
7772        if (file_exists($tempLocation_mp3)) {
7773            unlink($tempLocation_mp3);
7774        }
7775        if (file_exists($tempLocation_wav)) {
7776            unlink($tempLocation_wav);
7777        }
7778        move_uploaded_file(
7779            $qvalue_mp3['tmp_name'],
7780            $tempLocation_mp3
7781        );
7782        move_uploaded_file(
7783            $qvalue_wav['tmp_name'],
7784            $tempLocation_wav
7785        );
7786        // note this file is only temporary, so uploader_is is set to user_id only
7787        $uploader_id = $this->Auth->user('id');
7788
7789        $this->loadModel('FileStorage');
7790
7791        //-- upload chatbox audio recording
7792        $chaboxAudRecordUpload = $this->FileStorage->uploadFile(array(
7793            'uploader_id' => $uploader_id,
7794            'uploader_type' => 34,
7795            'source' => $tempLocation_mp3,
7796            'key' => $filename_mp3,
7797            'file' => $_FILES['audio_mp3'],
7798            'delete_image' => false
7799        ));
7800
7801        $resultUpload = $this->FileStorage->uploadFile(array(
7802            'uploader_id' => $uploader_id,
7803            'uploader_type' => 19, // 19 - Chivox file
7804            'source' => $tempLocation_wav,
7805            'key' => $filename_wav,
7806            'file' => $_FILES['audio_wav'],
7807            'delete_image' => true
7808        ));
7809
7810        // if aws audio is sucess request chivox thru java server
7811        if (isset($resultUpload['FileStorage']['url'])) {
7812
7813            // get chivox type
7814            $chivoxCountType = 4;
7815            $pj_identifier =  Configure::read('chivox.identifiers.textbook_speaking_training');
7816            if ($textbookCategory == Configure::read('chivox.textbook_categories.speaking_training')){
7817                $chivoxCountType = 4;
7818                $pj_identifier =  Configure::read('chivox.identifiers.textbook_speaking_training');
7819            } else if ($textbookCategory == Configure::read('chivox.textbook_categories.eiken_course') || $textbookCategory == Configure::read('chivox.textbook_categories.eiken_category')){
7820                $chivoxCountType = 5;
7821                $pj_identifier =  Configure::read('chivox.identifiers.textbook_eiken');
7822            } else if ($textbookCategory == Configure::read('chivox.textbook_categories.basicpronunciation_course') || $textbookCategory == Configure::read('chivox.textbook_categories.basicpronunciation_category')){
7823                $chivoxCountType = 6;
7824                $pj_identifier =  Configure::read('chivox.identifiers.textbook_basic_pronunciation');
7825            } else if ($textbookCategory == Configure::read('chivox.textbook_categories.intonationpronunciation_course') || $textbookCategory == Configure::read('chivox.textbook_categories.intonationpronunciation_category')){
7826                $chivoxCountType = 8;
7827                $pj_identifier =  Configure::read('chivox.identifiers.textbook_intonation_pronunciation');
7828            } else if ($textbookCategory == Configure::read('chivox.textbook_categories.linkingpronunciation_course') || $textbookCategory == Configure::read('chivox.textbook_categories.linkingpronunciation_category')){
7829                $chivoxCountType = 8;
7830                $pj_identifier =  Configure::read('chivox.identifiers.textbook_linking_pronunciation');
7831            } else if ($textbookCategory == Configure::read('chivox.textbook_categories.accentpronunciation_course') || $textbookCategory == Configure::read('chivox.textbook_categories.accentpronunciation_category')){
7832                $chivoxCountType = 8;
7833                $pj_identifier =  Configure::read('chivox.identifiers.textbook_accent_pronunciation');
7834            } else if ($textbookCategory == Configure::read('chivox.textbook_categories.americanaccentpronunciation_course') || $textbookCategory == Configure::read('chivox.textbook_categories.americanaccentpronunciation_category')){
7835                $chivoxCountType = 8;
7836                $pj_identifier =  Configure::read('chivox.identifiers.textbook_american_accent_pronunciation');
7837            } else if ($textbookCategory == Configure::read('chivox.textbook_categories.speaking_training_for_school')){
7838                $chivoxCountType = 13; // NJ-12005 - Speaking Training for School
7839                $pj_identifier =  Configure::read('chivox.identifiers.textbook_speaking_training_for_school');
7840            } else if ($textbookCategory == Configure::read('chivox.textbook_categories.speaking_training_for_business')){
7841                $chivoxCountType = 14; // NJ-1453 - Speaking Training for business
7842                $pj_identifier =  Configure::read('chivox.identifiers.textbook_speaking_training_for_business');
7843            } else if ($textbookCategory == Configure::read('chivox.textbook_categories.practicalpronunciation_training')){
7844                $chivoxCountType = 24; // NJ-30843 - Practical Pronunciation Textbook
7845                $pj_identifier =  Configure::read('chivox.identifiers.textbook_practical_pronunciation');
7846            } else if ($textbookCategory == Configure::read('chivox.textbook_categories.toeicspeakingtest_training')) {
7847                $chivoxCountType = 29; // NJ-38904 - TOEIC Speaking Test
7848                $pj_identifier = Configure::read('chivox.identifiers.textbook_toeic_speaking_test');
7849            } else if ($textbookCategory == Configure::read('chivox.textbook_categories.basicpronunciation_british_course') || $textbookCategory == Configure::read('chivox.textbook_categories.basicpronunciation_british_category')){
7850                $chivoxCountType = 31; // NJ-38904 - Basic Pronunciation British Accent
7851                $pj_identifier =  Configure::read('chivox.identifiers.textbook_basic_pronunciation_british');
7852            }
7853
7854            // environment
7855            $ENV = Configure::read('ENVIRONMENT');
7856            $uri = myTools::getChivoxURI($ENV);
7857            $uri = isset($uri['chivox_utility']) ? $uri['chivox_utility'] : "";
7858
7859            if (in_array($chivox_type, array(2,5,7,8,9,10,11))) {
7860                if ($chivox_type == 5) { // 5: Linking Pronunciation
7861                    $rangeRotalScore = $this->SettingOption->getOptions('chivox_linkingpron_total_score');
7862                    $rangeLetterScore = $this->SettingOption->getOptions('chivox_linkingpron_letter_score');
7863                } else if (in_array($chivox_type, array(7,8,9,10,11))) { // 7: Reduction American Accent || 8:9:10: T-Variation || 11: Shorten American Accent
7864                    $rangeRotalScore = $this->SettingOption->getOptions('chivox_american_total_score');
7865                    $rangeLetterScore = $this->SettingOption->getOptions('chivox_american_letter_score');
7866                    $originalText = explode(" ", $refText);
7867                    $keyText = $originalText[$keyword-1];
7868                    $keyword = str_replace([',', '.', '?'], '', $keyText);
7869                } else { // 2: Basic Pronunciation
7870                    $rangeRotalScore = $this->SettingOption->getOptions('chivox_basicpron_total_score');
7871                    $rangeLetterScore = $this->SettingOption->getOptions('chivox_basicpron_letter_score');
7872                }
7873
7874                $range = array_merge($rangeRotalScore, $rangeLetterScore);
7875
7876                $fields = array(
7877                    'action' => "audioPronunciation",
7878                    'env' => $ENV,
7879                    'filename' => $filename_wav,
7880                    'refText' => $refText,
7881                    'userRecordedAudioUri' => $resultUpload['FileStorage']['url'],
7882                    'sampleRate' => $sampleRate,
7883                    'score_keeptrying_from' => $range['score_keeptrying_from'],
7884                    'score_keeptrying_to' => $range['score_keeptrying_to'],
7885                    'score_good_from' => $range['score_good_from'],
7886                    'score_good_to' => $range['score_good_to'],
7887                    'score_excellent_from' => $range['score_excellent_from'],
7888                    'score_excellent_to' => $range['score_excellent_to'],
7889                    'score_perfect_from' => $range['score_perfect_from'],
7890                    'score_perfect_to' => $range['score_perfect_to'],
7891                    'word_point_green_from' => $range['word_point_green_from'],
7892                    'word_point_green_to' => $range['word_point_green_to'],
7893                    'word_point_red_from' => $range['word_point_red_from'],
7894                    'word_point_red_to' => $range['word_point_red_to'],
7895                    'kernel' => $kernel,
7896                    'keyword' => $keyword,
7897                    'user_id' => $userID,
7898                    'pj_identifier' => $pj_identifier
7899                );
7900                if ($chivox_type == 5) {
7901                    $fields['result_view'] = 'linking_score';
7902                } else if (in_array($chivox_type, array(8,9,10))) {
7903                    $fields['result_view'] = 'tvariation_score';
7904                }
7905                $this->log(json_encode($fields), 'debug');
7906            } else if ($chivox_type == 4 || $chivox_type == 14) {
7907                $fields = array(
7908                    'action' => "audioPronunciation",
7909                    'env' => $ENV,
7910                    'filename' => $filename_wav,
7911                    'refText' => $refText,
7912                    'userRecordedAudioUri' => $resultUpload['FileStorage']['url'],
7913                    'sampleRate' => $sampleRate,
7914                    'kernel' => $kernel,
7915                    'keyword' => $keyword,
7916                    'keyletterlocation' => $keyletterlocation,
7917                    'user_id' => $userID,
7918                    'pj_identifier' => $pj_identifier,
7919                    'result_view' => 'stress_score'
7920                );
7921
7922                // NJ-8445: temp fix before actual chivox side fix
7923                $phoneme = array('architecture','centimeter','competition', 'investment', 'mobile', 'motivation', 'politician', 'supermarket', 'yoghurt', 'popular');
7924                if (in_array($refText, $phoneme)) {
7925                    $fields['phoneme'] = 1;
7926                }
7927
7928                $this->log(json_encode($fields), 'debug');
7929            } else if ($chivox_type == 6) {
7930                $getIntonation = explode('-', json_decode($keyword));
7931                $getWords = explode(' ', $refText);
7932                $newRefText = array();
7933                if (!empty($getWords)) {
7934                    foreach ($getWords as $key => $value) {
7935                        if (in_array($key+1, $getIntonation)) {
7936                            $newValue = str_replace(',', '', $value);
7937                            $newValue = str_replace('?', '', $newValue);
7938                            $newRefText[] = $newValue."(s:1)";
7939                        } else {
7940                            $newRefText[] = $value;
7941                        }
7942                    }
7943                }
7944                $newRefText = implode(" ", $newRefText);
7945                $fields = array(
7946                    'action' => "audioPronunciation",
7947                    'env' => $ENV,
7948                    'filename' => $filename_wav,
7949                    'refText' => $newRefText,
7950                    'userRecordedAudioUri' => $resultUpload['FileStorage']['url'],
7951                    'sampleRate' => $sampleRate,
7952                    'kernel' => $kernel,
7953                    'keyword' => json_decode($keyword),
7954                    'keyletterlocation' => json_decode($keyletterlocation),
7955                    'user_id' => $userID,
7956                    'pj_identifier' => $pj_identifier,
7957                    'result_view' => 'intonation_score'
7958                );
7959                $this->log(json_encode($fields), 'debug');
7960            } else {
7961                $fields = array(
7962                    'action' => "audio",
7963                    'env' => $ENV,
7964                    'filename' => $filename_wav,
7965                    'refText' => $refText,
7966                    'userRecordedAudioUri' => $resultUpload['FileStorage']['url'],
7967                    'sampleRate' => $sampleRate,
7968                    'user_id' => $userID,
7969                    'pj_identifier' => $pj_identifier
7970                );
7971
7972                if (!empty($recitation)) {
7973                    $fields['recitation'] = $recitation;
7974                }
7975
7976                if ($kernel == 'en.pred.score') {
7977                    $fields['action'] = 'audioParagraph';
7978                }
7979            }
7980
7981            //NJ-37185: add accent
7982            if (in_array($kernel, ['en.word.pron', 'en.sent.pron']) && $accent == 1) {
7983                $fields['accent'] = $accent;
7984            }
7985            //NJ-36647: add device type
7986            $fields['device_type'] = "PC";
7987            $fields_string = "";
7988
7989            //url-ify the data for the POST
7990            foreach($fields as $key=>$value) { $fields_string .= $key.'='.$value.'&'; }
7991            rtrim($fields_string, '&');
7992
7993            $ch = curl_init($uri);
7994            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
7995            curl_setopt($ch, CURLOPT_HEADER, false);
7996            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
7997            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
7998
7999            curl_setopt($ch,CURLOPT_POST, count($fields));
8000            curl_setopt($ch,CURLOPT_POSTFIELDS, $fields_string);
8001
8002            $result = curl_exec($ch);
8003            curl_close($ch);
8004            $result = json_decode($result, true);
8005            
8006            // - NJ-3658 response error code
8007            $response['chivoxError'] = isset($result['rawScore']['errId'])? $result['rawScore']['errId'] : null;
8008            
8009            if (isset($result['success']) && $result['success'] && $result['rawScore']['errId'] != 41030) {
8010
8011                $response = $result;
8012                $this->log("[NC-9473] java response " . json_encode($response), 'debug');
8013
8014                // instantiate view
8015                $view = new View($this, false);
8016                $view->layout = false;
8017                
8018                $viewFile = 'reading_ai_onlesson_result';
8019                // default view data 
8020                $viewData = array(
8021                    'details' => $result['details'],
8022                    'scoreValue' => $result['scoreValue'],
8023                    'refText' => $refText,
8024                    'chivox_pc' => $chivox_pc,
8025                    'chivox_type' => $chivox_type
8026                );                
8027                
8028                if ($chivox_type == 2) {
8029                    $viewData['keyword'] = $keyword;
8030                    $viewData['keyletterlocation'] = $keyletterlocation;
8031                    $viewData['average_score'] = isset($result['average_score']) ? $result['average_score'] : '';
8032                    $viewFile = 'pronunciation_basics_onlesson_result';
8033                } else if ($chivox_type == 4 || $chivox_type == 14) {
8034                    $viewData['keyword'] = $keyword;
8035                    $viewData['keyletterlocation'] = $keyletterlocation;
8036                    $viewData['label_translations'] = $labelTranslations;
8037                    $viewFile = 'pronunciation_advance_onlesson_result';
8038                } else if ($chivox_type == 5) {
8039                    $viewData['keyword'] = $keyword;
8040                    $viewData['keyletterlocation'] = $keyletterlocation;
8041                    $viewData['average_score'] = isset($result['average_score']) ? $result['average_score'] : '';
8042                    $viewData['score_keeptrying_to'] = isset($range['score_keeptrying_to']) ? $range['score_keeptrying_to'] : 30;
8043                    $viewFile = 'pronunciation_linking_onlesson_result';
8044                } else if ($chivox_type == 6) {
8045                    $viewData['keyword'] = json_decode($keyword);
8046                    $viewData['keyletterlocation'] = json_decode($keyletterlocation);
8047                    $viewData['label_translations'] = $labelTranslations;
8048                    $viewFile = 'pronunciation_intonation_onlesson_result';
8049                } else if (in_array($chivox_type, array(7,8,9,10,11))) {
8050                    $viewData['keyword'] = $keyword;
8051                    $viewData['keyletterlocation'] = $keyletterlocation;
8052                    $viewData['average_score'] = isset($result['average_score']) ? $result['average_score'] : '';
8053                    $viewData['score_keeptrying_to'] = isset($range['score_keeptrying_to']) ? $range['score_keeptrying_to'] : 30;
8054                    $viewData['word_point_red_to'] = isset($range['word_point_red_to']) ? $range['word_point_red_to'] : 50;
8055                    $viewFile = 'pronunciation_usa_onlesson_result';
8056                } else {
8057                    $view->set(array(
8058                        'details' => $result['details'],
8059                        'scoreValue' => $result['scoreValue'],
8060                        'refText' => $refText,
8061                        'chivox_pc' => $chivox_pc,
8062                        'chivox_type' => $chivox_type,
8063                        "phonemes" => $phonemes                    
8064                    ));
8065                    $resultView = $view->element('reading_ai_onlesson_result');
8066                }
8067                
8068                if ( !empty($storedRecordingFlag) ) {
8069                    $fileUrl = $chaboxAudRecordUpload['FileStorage']['url'];
8070                    $response['audio_url'] = str_replace('https://', '', $fileUrl);
8071                    $response['textbook_name'] = '';
8072                    //-- user current language
8073                    $userLanguage = $this->User->fetchUserCurrentLanguage(array(
8074                        'user_id' => $userID,
8075                        'studentLessonLocalizeDir' => $studentLessonLocalizeDir
8076                    ));        
8077                    $textbookName = $this->TextbookConnect->getTextbookName(array(
8078                        'connect_id' => $connectId,
8079                        'native_language' => $userLanguage,
8080                        'translate_other_lang' => true
8081                    ));                    
8082                    $response['textbook_name'] .= !empty($textbookName['textbook_category_name']) ? $textbookName['textbook_category_name'] : '';
8083                    $response['textbook_name'] .= !empty($textbookName['textbook_subcategory_name']) ? ' ' . $textbookName['textbook_subcategory_name'] : '';
8084                    $response['textbook_name'] .= !empty($textbookName['textbook_name']) ? ' : ' . $textbookName['textbook_name'] : '';
8085
8086                    $viewData['audio_url'] = isset($chaboxAudRecordUpload['FileStorage']['url']) ? $chaboxAudRecordUpload['FileStorage']['url'] : "";
8087                    $viewData['question_name'] = !empty($questionName) ? $questionName : $textbookName['textbook_name'];
8088
8089                    if (in_array($chivox_type, [4, 6, 14])) {
8090                        $studentDeviceType = "";
8091
8092                        if (!empty($chatHash)) {
8093                            $this->LessonOnair->openDBReplica();
8094                            $userAgent = $this->LessonOnair->field('user_agent', array('LessonOnair.chat_hash' => $chatHash));
8095                            $this->LessonOnair->closeDBReplica();
8096                            
8097                            $studentDeviceType = myTools::getUsersDevice($userAgent);
8098                        }
8099
8100                        $viewData['label_translations'] = ($studentDeviceType != 'PC')
8101                            ? $this->getLabelTranslations($userLanguage)
8102                            : $labelTranslations;
8103
8104                        $this->log("users device and language: " . $studentDeviceType . " - " . $userLanguage, 'debug');
8105                        $this->log("label translations: " . json_encode($viewData['label_translations']), 'debug');
8106                    }
8107                }
8108
8109                // - old design result
8110                $resultView = $view->element($viewFile, $viewData);
8111                $response['resultView'] = $resultView;
8112                
8113                $resultViewStudent = $view->element('student_chat_class_chivox', $viewData);
8114                $viewData['question_name'] = !empty($questionName) ? $questionName : $textbookName['textbook_name_eng'];
8115                $resultViewTeacher = $view->element('teacher_chat_class_chivox', $viewData);
8116                $response['resultViewStudent'] = $resultViewStudent;
8117                $response['resultViewTeacher'] = $resultViewTeacher;
8118
8119            } else {
8120                if (isset($result['error']) && !empty($result['error'])) {
8121                    $errorName = myTools::removeNonLettersAndLowercase($result['error']);
8122                    //NJ-26204: check if cases, (3) and (4), of error code 41030
8123                    if (in_array($errorName, Configure::read('error_41030_triggers'))) {
8124                        $response['chivoxErrorId'] = $response['chivoxError'] = $result['errId'] = 41030;
8125                        $result['error_method'] = __METHOD__;
8126                        $result['error_url'] = @$_SERVER['REQUEST_URI'];
8127
8128                        // - initialize slack and send
8129                        App::uses('mySlack', 'Lib');
8130                        $mySlack = new mySlack();
8131
8132                        // - report chivox error to slack.
8133                        $mySlack->createChivoxErrorContent($result);
8134                        // - NJ-3658 updateTrainingStatus
8135                        $response['chivoxTrainingDataOff'] = $this->TrainingData->updateTrainingStatus();
8136
8137                        // turn on NC Content error modal
8138                        if(!$this->NCContentList->turnOnNCContentErrorModal()) {
8139                            $this->log('NJ-26204 Failed to turn ON error modal flg.', 'debug');
8140                        }
8141                    }
8142                } else {
8143                    $response['error'] = !empty($result['error']) ? $result['error'] : "Chivox Error!";
8144                    // report chivox error to slack.
8145                    $this->sendChivoxSlack($result);
8146                }
8147            }
8148
8149            // return chivox type for app
8150            $response['chivox_type'] = $chivox_type;
8151            $params = array(
8152                "pj_identifier" => $pj_identifier,
8153                "user_id" => $userID,
8154                "env" => $ENV,
8155                "device_type" => "PC"
8156            );
8157            $getChivoxIdentifier = myTools::getChivoxIdentifier($params);
8158
8159            // get query_date
8160            $currentDay = date('Y-m-d');
8161            $queryCountToSave = array(
8162                'user_id' => $userID,
8163                'query_date' => $currentDay,
8164                'chivox_type' => $chivoxCountType,
8165                'chivox_callan_pp_id' => null,
8166                'chivox_monthly_test_id' => null,
8167                'chivox_textbook_category' => $textbookCategory,
8168                'question_count' => 1,
8169                'chivox_identifier' => $getChivoxIdentifier
8170            );
8171            $this->UsersChivoxCountQuery->updateQuestionQueryCount($queryCountToSave);
8172            // Delete aws
8173            $del = $this->FileStorage->removeFile(array("url" => $resultUpload['FileStorage']['url']));
8174            if (!$del) {
8175                $this->log('[NC-8372 uploadRecordedFileChivox Fail deleting the audio from File storage, AWS. ' . json_encode($resultUpload['FileStorage']['url']), 'debug');
8176            }
8177        }
8178        return json_encode($response);
8179    }
8180
8181    /**
8182     * @api {post} /teacher/api/saveConnectionSpeed saveConnectionSpeed()
8183     * @apiName saveConnectionSpeed
8184     * @apiGroup API
8185     * @apiDescription save teacher connection speed every 10 seconds
8186     * @apiSampleRequest off
8187     * 
8188     * @apiBody {Array} connection_speed The connection speed.
8189     * 
8190     * @apiSuccess {Boolean} success The status of the request.
8191     * 
8192     * @apiErrorExample {json} Success-Response:
8193     *     {
8194     *       "success": true
8195     *     }
8196     *
8197     * @apiErrorExample {js} Used in: Elements
8198     * Location: "view/Elements/chat_area_js.php"
8199     */
8200    /**
8201     * save teacher connection speed every 10 seconds
8202     */
8203    public function saveConnectionSpeed() {
8204        $this->layout = $this->autoRender = false;
8205        $res = array('success' => false);
8206        if ($this->request->is('post')) {
8207            try {
8208                if (isset($this->request->data['connection_speed']) && is_array($this->request->data['connection_speed'])) {
8209                    $logValues = ClassRegistry::init('LessonConnectionSpeedLog')->composeDataToInsert($this->request->data['connection_speed']);
8210                    $model = ClassRegistry::init("LogModel");
8211                    $query = $model->query("INSERT INTO 
8212                                                        lesson_connection_speed_logs
8213                                                        (chat_hash, user_id, teacher_id, download_speed, upload_speed, created, modified, created_ip, modified_ip)
8214                                                    VALUES 
8215                                                        $logValues");
8216                    if ($query) {
8217                        $res = array('success' => true);
8218                    } else {
8219                        $this->log(__METHOD__ . ' save_to_lesson_connection_speed_logs_failed', 'debug');
8220                    }
8221                }
8222            } catch(Exception $csl) {
8223                $this->log('[LESSON CONNECTION SPEED LOG SAVE FAILED] Caught Exception: ' . $csl->getMessage() . ' : ' . json_encode($this->request->data['connection_speed']), 'debug');
8224                $res = array('success' => false);
8225            }
8226        }
8227
8228        return json_encode($res);
8229    }
8230
8231    /**
8232     * @api {get} /teacher/api/disconnectionCancellationMemCached disconnectionCancellationMemCached()
8233     * @apiName disconnectionCancellationMemCached
8234     * @apiGroup API
8235     * @apiDescription Check if teacher has a recent canceled lesson(reservation) OR remove lesson cancelation memcached.
8236     * @apiSampleRequest off
8237     * 
8238     * @apiBody {String} checker The type of action to perform (check or delete).
8239     * 
8240     * @apiSuccess {Boolean} has_lessonCancel The status of the request.
8241     * 
8242     * @apiErrorExample {json} Success-Response:
8243     *     {
8244     *       "has_lessonCancel": true
8245     *     }
8246     *
8247     * @apiErrorExample {json} Error-Response:
8248     *     {
8249     *       "has_lessonCancel": false
8250     *     }
8251     * 
8252      * @apiErrorExample {js} Used in: JS
8253     * Location: "view/Home/index.php"
8254     */
8255    /**
8256    * Check if teacher has a recent canceled lesson(reservation) OR remove lesson cancelation memcached
8257    * @return bool
8258    */
8259    public function disconnectionCancellationMemCached () {
8260        $this->layout = $this->autoRender = false;
8261        $checkerType = (isset($this->request->query['checker'])) ? $this->request->query['checker'] : null;
8262        $res = array('has_lessonCancel' => false);
8263        $memcached = new myMemcached();
8264        $memKey = 'memDisconnectionCancellation_' . $this->Auth->user('id');
8265        $hasLessonCancel = $memcached->get($memKey);
8266        if ($hasLessonCancel) {
8267            if($checkerType == 'check') {
8268                $res['has_lessonCancel'] = true;
8269            } else if($checkerType == 'delete') {
8270                $memcached->delete($memKey);
8271            }
8272        }
8273        return json_encode($res);    
8274    }
8275
8276    /**
8277     * @api {post} /teacher/api/uploadAttachedFileToAWS uploadAttachedFileToAWS()
8278     * @apiName uploadAttachedFileToAWS
8279     * @apiGroup API
8280     * @apiDescription upload file aws and get equivalnet s3 urls
8281     * @apiSampleRequest off
8282     * 
8283     * @apiBody {File} attached_files The file to upload.
8284     * @apiBody {String} fileUrl The URL of the file.
8285     * @apiBody {String} refText The reference text.
8286     * 
8287     * @apiSuccess {String} fileUrl The URL of the file.
8288     * 
8289     * @apiErrorExample {json} Success-Response:
8290     *     {
8291     *       "fileUrl": "https://s3.amazonaws.com/bucket/filename.ext"
8292     *     }
8293     *
8294     * @apiErrorExample {json} Error-Response:
8295     *     {
8296     *       "fileUrl": ""
8297     *     }
8298     * 
8299     * @apiErrorExample {js} Used in: Elements
8300     * Location: "view/Elements/chat_area_js.php"
8301     */
8302    /**
8303    * upload file aws and get equivalnet s3 urls
8304    * @return string $fileUrl
8305    */
8306   public function uploadAttachedFileToAWS() {
8307        $this->autoRender = false;
8308        $fileUrl = '';
8309        // - check if request has a valid file
8310        if (!isset($_FILES['attached_files']['size']) || !$_FILES['attached_files']['size']) {
8311            return $fileUrl;
8312        }
8313        $fileSelected = $_FILES['attached_files'];
8314        $containerFileTemp = ROOT . '/instructor/webroot/files';
8315        if (!is_dir($containerFileTemp)) {
8316            mkdir($containerFileTemp);
8317        }        
8318        $filename     = time() . uniqid() . '_' . basename($fileSelected['name']);
8319        $full_url     = $containerFileTemp .'/'. $filename;
8320        $success     = move_uploaded_file($fileSelected['tmp_name'], $full_url);
8321        if ($success) {
8322            $tempResult = ClassRegistry::init('FileStorage')->uploadFile(array(
8323                'uploader_id'     => $this->Auth->user('id'),
8324                'uploader_type' => 26,
8325                'source'         => $full_url,
8326                'key'             => $filename,
8327                'file'             => $fileSelected,
8328                'delete_image'     => true
8329            ));
8330            $fileUrl = $tempResult['FileStorage']['url'];
8331        }
8332        return $fileUrl;
8333    }
8334
8335    /**
8336     * @api {get} /teacher/api/downloadChatLogFiles downloadChatLogFiles()
8337     * @apiName downloadChatLogFiles
8338     * @apiGroup API
8339     * @apiDescription download file from chatlogs
8340     * @apiSampleRequest off
8341     * 
8342     * @apiBody {String} file The URL of the file to download.
8343     *
8344     * @apiErrorExample {js} Used in: Elements
8345     * Location: "view/Elements/chat_area_js.php"
8346     * Location: "view/Elements/lesson_message_js.php"
8347     * 
8348     * @apiErrorExample {js} Used in: WebRTC
8349      * Location: "webroot/js/webrtcv2/event.student.js"
8350     */
8351    /**
8352    * download file from chatlogs
8353    */
8354    public function downloadChatLogFiles() {
8355        $this->autoRender = false;
8356        if (isset($this->request->query['file'])) {
8357            $fileUrl     = $this->request->query['file'];
8358            $remoteFile = str_replace(basename($fileUrl), urlencode(basename($fileUrl)), $fileUrl);
8359            $fileUrl     = basename($this->request->query['file']);
8360            $fileName     = explode('_', $fileUrl);
8361            $fileName     = isset($fileName[1]) ? str_replace($fileName[0].'_', '', $fileUrl) : $fileUrl;
8362            $tempFileDir = ROOT . '/instructor/webroot/files/' . $fileUrl;
8363             
8364            // ~ get file content from s3
8365            $cURLConnection = curl_init();
8366            curl_setopt($cURLConnection, CURLOPT_URL, $remoteFile);
8367            curl_setopt($cURLConnection, CURLOPT_RETURNTRANSFER, true);
8368            curl_setopt($cURLConnection, CURLOPT_HTTPHEADER, array('Referer: '.myTools::getUrl().'/'));
8369            $fileContent = curl_exec($cURLConnection);
8370            curl_close($cURLConnection);
8371
8372            file_put_contents($tempFileDir, $fileContent);
8373            header('Content-Length: '. filesize($tempFileDir));
8374            header('Content-Type: application/octet-stream');
8375            header('Content-Disposition: attachment; filename='. $fileName);
8376            readfile($tempFileDir);
8377            
8378            // - delete audio file if it exist
8379            if (file_exists($tempFileDir)) {
8380                unlink($tempFileDir);
8381            }
8382        }
8383        exit;
8384    }
8385
8386    /**
8387     * @api {post} /teacher/api/uploadeCancelDeleteFile uploadeCancelDeleteFile()
8388     * @apiName uploadeCancelDeleteFile
8389     * @apiGroup API
8390     * @apiDescription delete file when canceled
8391     * @apiSampleRequest off
8392     * 
8393     * @apiBody {Array} fileUrl The URL of the file to delete.
8394     * 
8395     * @apiSuccess {Boolean} success The status of the request.
8396     * 
8397     * @apiErrorExample {json} Success-Response:
8398     *     {
8399     *       "success": true
8400     *     }
8401     *
8402     * @apiErrorExample {js} Used in: Elements
8403     * Location: "view/Elements/chat_area_js.php"
8404     */
8405    /**
8406    * delete file when canceled
8407    */
8408    public function uploadeCancelDeleteFile() {
8409        $this->autoRender = false;
8410        $file = array();
8411        if (isset($this->request->data['fileUrl']) && $this->request->data['fileUrl']) {
8412            foreach ($this->request->data['fileUrl'] as $fileKey => $fileUrl) {
8413                ClassRegistry::init('FileStorage')->removeFile(array('url' => 'https://' . $fileUrl));
8414            }
8415        }
8416        return true;
8417    }
8418
8419    /**
8420     * @api {get} /teacher/api/uploadRecordedFileMerge uploadRecordedFileMerge()
8421     * @apiName uploadRecordedFileMerge
8422     * @apiGroup API
8423     * @apiDescription Save audio to aws, use to request chivox ai and google api merge
8424     * @apiSampleRequest off
8425     * 
8426     * @apiBody {File} audio The audio file to upload.
8427     * @apiBody {String} refText The reference text.
8428     * @apiBody {String} chivox_pc The chivox pc.
8429     * 
8430     * @apiSuccess {Boolean} success The status of the request.
8431     * @apiSuccess {String} filename The name of the file.
8432     * @apiSuccess {String} audioFileURL The URL of the audio file.
8433     * @apiSuccess {String} convertedSpeech The converted text.
8434     * @apiSuccess {String} resultView The HTML content.
8435     * 
8436     * @apiErrorExample {json} Success-Response:
8437     *     {
8438     *       "success": true,
8439     *       "filename": "filename.wav",
8440     *       "audioFileURL": "url",
8441     *       "convertedSpeech": "Converted text",
8442     *       "resultView": "HTML content"
8443     *     }
8444     *
8445      * @apiErrorExample {js} Used in: JS
8446      * Location: "webroot/js/custom_sound-recorder.js"
8447     */
8448    /**
8449    * Save audio to aws, use to request chivox ai and google api merge
8450    * @return mixed - json $response
8451    */
8452    public function uploadRecordedFileMerge () {
8453        $this->autoRender = false;
8454        $convertedSpeech = null;
8455        $response['success'] = false;
8456
8457        // - check if request has a valid file
8458        if (
8459            (!isset($_FILES['audio_wav']['size']) || !$_FILES['audio_wav']['size'])
8460            || (!isset($_FILES['audio_mp3']['size']) || !$_FILES['audio_mp3']['size'])
8461            || empty($this->request->data['refText'])
8462        ) {
8463            $response['error_code'] = "missing_parameters " . json_encode($_FILES);
8464            $response['error_code'] = "missing_parameters " . json_encode($this->request->data['refText']);
8465            return json_encode($response);
8466        }
8467
8468        $qvalue_mp3 = $_FILES['audio_mp3'];
8469        $qvalue_wav = $_FILES['audio_wav'];
8470        $chivox_pc = isset($this->request->data['chivox_pc']) ? $this->request->data['chivox_pc'] : 0;
8471        $rawRefText = isset($this->request->data['refText']) ? $this->request->data['refText'] : "";
8472        $chivoxSampleRate = Configure::read('chivox.sampleRate');
8473        $sampleRate = isset($this->request->data['sampleRate']) && $this->request->data['sampleRate'] <= $chivoxSampleRate ? $this->request->data['sampleRate'] : $chivoxSampleRate;
8474        $chivox_type = isset($this->request->data['chivox_type']) ? $this->request->data['chivox_type'] : 1;
8475        $kernel = isset($this->request->data['kernel']) ? $this->request->data['kernel'] : null;
8476        $addParams = isset($this->request->data['addParams']) ? json_decode($this->request->data['addParams'], true) : null;
8477        $qIndex = isset($addParams['qIndex']) ? $addParams['qIndex'] : "";
8478        $sendSuccessLamp = isset($addParams['sendSuccessLamp']) ? $addParams['sendSuccessLamp'] : "";
8479        $sendResult = isset($addParams['sendResult']) ? $addParams['sendResult'] : "";
8480        $reftextKeyword = isset($addParams['refTextAnswerKeyword']) ? $addParams['refTextAnswerKeyword'] : "";
8481        $targetKey = isset($addParams['targetKey']) ? $addParams['targetKey'] : "";
8482        $userID = isset($this->request->data['userID']) ? $this->request->data['userID'] : 0;
8483        $textbookCategory = isset($this->request->data['textbookCategory']) ? $this->request->data['textbookCategory'] : null;
8484        $connectId = !empty($this->request->data['connect_id']) ?$this->request->data['connect_id'] : 0;
8485        $storedRecordingFlag = !empty($this->request->data['store_audio_record']) ? true : false;
8486        $storedRecordingDetails = array();
8487        $newReviewResultFlg = isset($this->request->data['newReviewResultFlg']) ? $this->request->data['newReviewResultFlg'] : 0;
8488        $questionName    = !empty($this->request->data['question_name']) ? $this->request->data['question_name'] : null;
8489
8490        if ($kernel == 'en.sent.score' || $kernel == 'en.pred.score') {
8491            $rawRefText = json_decode($rawRefText,true);
8492            $refText = $rawRefText[0];
8493        } else {
8494            $refText = $rawRefText;
8495        }
8496
8497        // - Get audio temporary folder
8498        $audioContainer = ROOT . '/user/webroot/files';
8499        if (!is_dir($audioContainer)) {
8500            mkdir($audioContainer);
8501        }
8502
8503        //upload to amazon server
8504        $filename_mp3 = uniqid() . "_" . $_FILES['audio_mp3']['name'] . '.mp3';
8505        $filename_wav = uniqid() . "_" . $_FILES['audio_wav']['name'] . '.wav';
8506        //temporary location
8507        $tempLocation_mp3 = ROOT . '/user/webroot/files/'. $filename_mp3;
8508        $tempLocation_wav = ROOT . '/user/webroot/files/'. $filename_wav;
8509
8510        // - delete audio file if already exist
8511        if (file_exists($tempLocation_mp3)) {
8512            unlink($tempLocation_mp3);
8513        }
8514        if (file_exists($tempLocation_wav)) {
8515            unlink($tempLocation_wav);
8516        }
8517        move_uploaded_file(
8518            $qvalue_mp3['tmp_name'],
8519            $tempLocation_mp3
8520        );
8521        move_uploaded_file(
8522            $qvalue_wav['tmp_name'],
8523            $tempLocation_wav
8524        );
8525        
8526        // note this file is only temporary, so uploader_is is set to user_id only
8527        $uploader_id = $this->Auth->user('id');
8528
8529        // NC-9388: START CHIVOX PR0CESS
8530        $this->loadModel('FileStorage');
8531
8532        //-- append chatbox audio recoring details
8533        if ( !empty($storedRecordingFlag) ) {
8534            $chatboxAudRecordUpload = $this->FileStorage->uploadFile(array(
8535                'uploader_id' => $uploader_id,
8536                'uploader_type' => 34,
8537                'source' => $tempLocation_mp3,
8538                'key' => $filename_mp3,
8539                'file' => $_FILES['audio_mp3'],
8540                'delete_image' => false
8541            ));
8542        
8543            $fileUrl = $chatboxAudRecordUpload['FileStorage']['url'];
8544            $storedRecordingDetails['audio_url'] = str_replace('https://', '', $fileUrl);
8545            $storedRecordingDetails['textbook_name'] = '';
8546            //-- user current language
8547            $userLanguage = $this->User->fetchUserCurrentLanguage(array(
8548                'user_id' => $userID
8549            ));    
8550            $textbookName = $this->TextbookConnect->getTextbookName(array(
8551                'connect_id' => $connectId,
8552                'native_language' => $userLanguage,
8553                'translate_other_lang' => true
8554            ));                    
8555            $storedRecordingDetails['textbook_name'] .= !empty($textbookName['textbook_category_name']) ? $textbookName['textbook_category_name'] : '';
8556            $storedRecordingDetails['textbook_name'] .= !empty($textbookName['textbook_subcategory_name']) ? ' ' . $textbookName['textbook_subcategory_name'] : '';
8557            $storedRecordingDetails['textbook_name'] .= !empty($textbookName['textbook_name']) ? ' : ' . $textbookName['textbook_name'] : '';
8558        }
8559
8560        // - upload chivox
8561        $resultUpload = $this->FileStorage->uploadFile(array(
8562            'uploader_id' => $uploader_id,
8563            'uploader_type' => 19, // 19 - Chivox file
8564            'source' => $tempLocation_wav,
8565            'key' => $filename_wav,
8566            'file' => $_FILES['audio_wav'],
8567            'delete_image' => true
8568        ));
8569        
8570        // NC-9388: START GOOGLE API SPEECH TO TEXT PR0CESS
8571        if (isset($resultUpload['FileStorage']['url'])) {
8572            App::uses('myRedis', 'Lib');
8573            
8574            // - get speech
8575            $responseGoogle = myTools::performGooglSpeechToText([
8576                'audio_url' => $resultUpload['FileStorage']['url'],
8577                'user_language' => $userLanguage
8578            ]);
8579            
8580            // - check if success
8581            if ($responseGoogle['success']) {
8582                $response['success'] = true;
8583                $convertedSpeech = $responseGoogle['convertedSpeech'];
8584
8585                if (!empty($responseGoogle['totalTime']) && is_array($responseGoogle['totalTime'])) {
8586                    //get word timestamps
8587                    $sum = 0;
8588                    foreach ($responseGoogle['totalTime'] as $key => $value) {
8589                        foreach ($value as $key2 => $value2) {
8590                            //get each word start and end time
8591                            $startTime = floatval($value2['startTime']);
8592                            $endTime = floatval($value2['endTime']);
8593                            // sum the difference of start and end time
8594                            $sum += (float)($endTime -  $startTime);
8595                        }
8596                    }
8597
8598                    //save speech to text logs
8599                    $logParams = array(
8600                        'service' => 2,
8601                        'type' => 3,
8602                        'controller' => static::class,
8603                        'method' => __METHOD__,
8604                        'url' => $this->request->here(),
8605                        'value'    => number_format($sum, 2, '.', '')
8606                    );
8607                    $googleLogs = ClassRegistry::init('GoogleTranslateLog');
8608                    $result = $googleLogs->saveLog($logParams);
8609                    //check if logs saved
8610                    if (!$result) {
8611                        $this->log('error', __METHOD__ . " -- [Google Translate] Unable to save logs. ");
8612                    }
8613                }
8614            } else {
8615                $response['error_code'] = "unable to save to google " . json_encode($responseGoogle);
8616            }
8617        }
8618        // NC-9388: END GOOGLE API SPEECH TO TEXT PR0CESS
8619        
8620        // if aws audio is sucess request chivox thru java server
8621        if (isset($resultUpload['FileStorage']['url'])) {
8622
8623            // get chivox type: eiken for this textbook
8624            $chivoxCountType = 5;
8625
8626            // environment
8627            $ENV = Configure::read('ENVIRONMENT');
8628            $uri = myTools::getChivoxURI($ENV);
8629            $uri = isset($uri['chivox_utility']) ? $uri['chivox_utility'] : "";
8630
8631            $pjIdentifierParams = array(
8632                "pj_identifier" => Configure::read('chivox.identifiers.textbook_eiken'),
8633                "user_id" => $userID,
8634                "env" => $ENV,
8635                "device_type" => "PC"
8636            );
8637            $pj_identifier = myTools::getChivoxIdentifier($pjIdentifierParams);
8638
8639            if ($kernel == 'en.sent.score' || $kernel == 'en.pred.score') {
8640                $fields = array(
8641                    'action' => "audio",
8642                    'env' => $ENV,
8643                    'filename' => $filename_wav,
8644                    'refText' => $refText,
8645                    'userRecordedAudioUri' => $resultUpload['FileStorage']['url'],
8646                    'sampleRate' => $sampleRate,
8647                    'user_id' => $userID,
8648                    'pj_identifier' => $pj_identifier
8649                );
8650
8651                if ($kernel == 'en.pred.score') {
8652                    $fields['action'] = 'audioParagraph';
8653                }
8654
8655            } else {
8656                $fields = array(
8657                    'action' => "audioInterview",
8658                    'env' => $ENV,
8659                    'filename' => $filename_wav,
8660                    'refText' => $refText,
8661                    'userRecordedAudioUri' => $resultUpload['FileStorage']['url'],
8662                    'sampleRate' => $sampleRate,
8663                    'keywords' => $reftextKeyword,
8664                    'kernel' => $kernel,
8665                    'user_id' => $userID,
8666                    'pj_identifier' => $pj_identifier
8667                );
8668            }
8669
8670            //NJ-36647: add device type
8671            $fields['device_type'] = "PC";
8672
8673            $fields_string = "";
8674
8675            //url-ify the data for the POST
8676            foreach($fields as $key=>$value) { $fields_string .= $key.'='.$value.'&'; }
8677            rtrim($fields_string, '&');
8678
8679            $ch = curl_init($uri);
8680            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
8681            curl_setopt($ch, CURLOPT_HEADER, false);
8682            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
8683            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
8684            curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 300); // set to 5 minutes
8685            curl_setopt($ch, CURLOPT_TIMEOUT, 300); // set to 5 minutes
8686            curl_setopt($ch, CURLOPT_POST, count($fields));
8687            curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string);
8688
8689            $result = curl_exec($ch);
8690            curl_close($ch);
8691            $result = json_decode($result, true);
8692
8693            if (isset($result['success']) && $result['success']) {
8694
8695                $response = $result;
8696                $response['raw_average_score'] = $result['average_score'];
8697                $getEquivalent = $this->getTextbookEquivalentScores($uploader_id, $targetKey);
8698                $response['average_score'] = isset($getEquivalent[$result['average_score']]) ? $getEquivalent[$result['average_score']] : $result['average_score'];
8699
8700                $viewFile = '';
8701                $viewData = array();
8702
8703                //NJ-7148
8704                if ($newReviewResultFlg) {
8705                    $viewData = array(
8706                        'details' => $result['details'],
8707                        'scoreValue' => $result['scoreValue'],
8708                        'average_score' => $response['average_score'],
8709                        'convertedSpeech' => $convertedSpeech,
8710                        'target_key' => $targetKey,
8711                        'chivox_type' => $chivox_type,
8712                        'review_flg' => 1,
8713                        'audio_url' => $fileUrl
8714                    );
8715                    $viewFile = 'eiken_mock_interview_review_result';
8716                } else {
8717                    if ($kernel == 'en.sent.score' || $kernel == 'en.pred.score') {
8718                        $viewData = array(
8719                            'details' => $result['details'],
8720                            'scoreValue' => $result['scoreValue'],
8721                            'refText' => $refText,
8722                            'audio_url' => $fileUrl,
8723                            'average_score' => $response['average_score'],
8724                            'chivox_type' => $chivox_type,
8725                            'target_key' => $targetKey
8726                        );
8727                        $viewFile = 'reading_ai_onlesson_result';
8728                    } else {
8729                        $viewData = array(
8730                            'average_score' => $response['average_score'],
8731                            'target_key' => $targetKey,
8732                            'audio_url' => $fileUrl,
8733                            'convertedSpeech' => $convertedSpeech,
8734                            'chivox_type' => 0 // - force set to 0 for display purposes
8735                        );
8736
8737                        $response['resultView'] = $convertedSpeech;
8738                    }
8739                }
8740
8741                if (!empty($viewData)) {
8742                    // instantiate view
8743                    $view = new View($this, false);
8744                    $view->layout = false;
8745
8746                    // - old design
8747                    if (!empty($viewFile)) {
8748                        $response['resultViewOld'] = $view->element($viewFile, $viewData);
8749                    }
8750
8751                    $resultViewStudent = $view->element('student_chat_class_chivox', $viewData);
8752                    $resultViewTeacher = $view->element('teacher_chat_class_chivox', $viewData);
8753                    
8754                    $response['resultViewStudent'] = $resultViewStudent;
8755                    $response['resultViewTeacher'] = $resultViewTeacher;
8756                }    
8757                
8758
8759                $this->log("file merge rsponse: " . json_encode($response));
8760            } else {
8761                $response['error'] = !empty($result['error']) ? $result['error'] : "Chivox Error!";
8762                // report chivox error to slack.
8763                $this->sendChivoxSlack($result);
8764            }
8765
8766            // return chivox type for app
8767            $params = array(
8768                "pj_identifier" => $pj_identifier,
8769                "user_id" => $userID,
8770                "env" => $ENV
8771            );
8772            $getChivoxIdentifier = myTools::getChivoxIdentifier($params);
8773
8774            // get query_date
8775            $currentDay = date('Y-m-d');
8776            $queryCountToSave = array(
8777                'user_id' => $userID,
8778                'query_date' => $currentDay,
8779                'chivox_type' => $chivoxCountType,
8780                'chivox_callan_pp_id' => null,
8781                'chivox_monthly_test_id' => null,
8782                'chivox_textbook_category' => $textbookCategory,
8783                'question_count' => 1,
8784                'chivox_identifier' => $getChivoxIdentifier
8785            );
8786            $this->UsersChivoxCountQuery->updateQuestionQueryCount($queryCountToSave);
8787
8788            // Delete aws
8789            $del = $this->FileStorage->removeFile(array("url" => $resultUpload['FileStorage']['url']));
8790            if (!$del) {
8791                $this->log('[NC-8372 uploadRecordedFileChivox Fail deleting the audio from File storage, AWS. ' . json_encode($resultUpload['FileStorage']['url']), 'debug');
8792            }
8793        }
8794        // NC-9388: END CHIVOX PR0CESS
8795        
8796        // - set converted speech
8797        $response['convertedSpeech'] = $convertedSpeech;
8798        if ( !empty($storedRecordingDetails) ) {
8799            $response = array_merge($response, $storedRecordingDetails);
8800        } else {
8801            $response['error_code'] = "unable to store audio file " . json_encode($storedRecordingDetails);
8802        }
8803        
8804        // - return
8805        return json_encode($response);
8806    }
8807
8808    /**
8809     * @api {post} /teacher/api/uploadRecordedFileMergeAsync uploadRecordedFileMergeAsync()
8810     * @apiName uploadRecordedFileMergeAsync
8811     * @apiGroup API
8812     * @apiDescription Save audio to aws, use to request chivox ai and google api merge
8813     * @apiSampleRequest off
8814     * 
8815     * @apiBody {File} audio The audio file to upload.
8816     * @apiBody {String} refText The reference text.
8817     * @apiBody {String} userID The user ID.
8818     * @apiBody {String} connect_id The connect ID.
8819     * @apiBody {String} convertedSpeech The converted speech.
8820     * @apiBody {String} store_audio_record The store audio record.
8821     * 
8822     * @apiSuccess {Boolean} success The status of the request.
8823     * @apiSuccess {String} filename The name of the file.
8824     * @apiSuccess {String} audioFileURL The URL of the audio file.
8825     * @apiSuccess {String} convertedSpeech The converted text.
8826     * @apiSuccess {String} resultView The HTML content.
8827     * 
8828     * @apiErrorExample {json} Success-Response:
8829     *     {
8830     *       "success": true,
8831     *       "filename": "filename.wav",
8832     *       "audioFileURL": "url",
8833     *       "convertedSpeech": "Converted text",
8834     *       "resultView": "HTML content"
8835     *     }
8836     *
8837     * @apiErrorExample {json} Error-Response:
8838     *     {
8839     *       "success": false,
8840     *       "error": "Chivox Error!"
8841     *     }
8842     */
8843    /**
8844     * Save audio to aws, use to request chivox ai and google api merge
8845     * @return mixed - json $response
8846     */
8847    public function uploadRecordedFileMergeAsync()
8848    {
8849        $this->autoRender = false;
8850        $response['success'] = false;
8851        $convertedSpeech = isset($this->request->data['convertedSpeech']) ? $this->request->data['convertedSpeech'] : '';
8852        $userID = isset($this->request->data['userID']) ? $this->request->data['userID'] : 0;
8853        $storedRecordingDetails = array();
8854        $storedRecordingFlag = !empty($this->request->data['store_audio_record']) ? true : false;
8855
8856        // note this file is only temporary, so uploader_is is set to user_id only
8857        $uploader_id = $this->Auth->user('id');
8858
8859        if (empty($convertedSpeech)) {
8860            $connectId = !empty($this->request->data['connect_id']) ? $this->request->data['connect_id'] : 0;
8861
8862            // - check if request has a valid file
8863            if (
8864                (!isset($_FILES['audio_wav']['size']) || !$_FILES['audio_wav']['size']) 
8865                || (!isset($_FILES['audio_mp3']['size']) || !$_FILES['audio_mp3']['size']) 
8866                || empty($this->request->data['refText'])
8867            ) {
8868                return json_encode($response);
8869            }
8870            $qvalue_mp3 = $_FILES['audio_mp3'];
8871            $qvalue_wav = $_FILES['audio_wav'];
8872
8873            // - Get audio temporary folder
8874            $audioContainer = ROOT . '/user/webroot/files';
8875            if (!is_dir($audioContainer)) {
8876                mkdir($audioContainer);
8877            }
8878
8879            //upload to amazon server
8880            $filename_mp3 = uniqid() . "_" . $_FILES['audio_mp3']['name'] . '.mp3';
8881            $filename_wav = uniqid() . "_" . $_FILES['audio_wav']['name'] . '.wav';
8882            //temporary location
8883            $tempLocation_mp3 = ROOT . '/user/webroot/files/'. $filename_mp3;
8884            $tempLocation_wav = ROOT . '/user/webroot/files/'. $filename_wav;
8885
8886            // - delete audio file if already exist
8887            if (file_exists($tempLocation_mp3)) {
8888                unlink($tempLocation_mp3);
8889            }
8890            if (file_exists($tempLocation_wav)) {
8891                unlink($tempLocation_wav);
8892            }
8893
8894            move_uploaded_file(
8895                $qvalue_mp3['tmp_name'],
8896                $tempLocation_mp3
8897            );
8898            move_uploaded_file(
8899                $qvalue_wav['tmp_name'],
8900                $tempLocation_wav
8901            );
8902
8903            // NC-9388: START CHIVOX PR0CESS
8904            $this->loadModel('FileStorage');
8905
8906            //-- append chatbox audio recoring details
8907            if (!empty($storedRecordingFlag)) {
8908                //-- upload chatbox audio recording
8909                $chatboxAudRecordUpload = $this->FileStorage->uploadFile(array(
8910                    'uploader_id' => $uploader_id,
8911                    'uploader_type' => 34,
8912                    'source' => $tempLocation_mp3,
8913                    'key' => $filename_mp3,
8914                    'file' => $_FILES['audio_mp3'],
8915                    'delete_image' => false
8916                ));
8917                
8918                //-- upload chatbox audio recording
8919                $fileUrl = $chatboxAudRecordUpload['FileStorage']['url'];
8920                $storedRecordingDetails['audio_url'] = str_replace('https://', '', $fileUrl);
8921                $storedRecordingDetails['textbook_name'] = '';
8922                //-- user current language
8923                $userLanguage = $this->User->fetchUserCurrentLanguage(array(
8924                    'user_id' => $userID
8925                ));
8926                $textbookName = $this->TextbookConnect->getTextbookName(array(
8927                    'connect_id' => $connectId,
8928                    'native_language' => $userLanguage,
8929                    'translate_other_lang' => true
8930                ));
8931                $storedRecordingDetails['textbook_name'] .= !empty($textbookName['textbook_category_name']) ? $textbookName['textbook_category_name'] : '';
8932                $storedRecordingDetails['textbook_name'] .= !empty($textbookName['textbook_subcategory_name']) ? ' ' . $textbookName['textbook_subcategory_name'] : '';
8933                $storedRecordingDetails['textbook_name'] .= !empty($textbookName['textbook_name']) ? ' : ' . $textbookName['textbook_name'] : '';
8934            }
8935
8936            // - upload file
8937            $resultUpload = $this->FileStorage->uploadFile(array(
8938                'uploader_id' => $uploader_id,
8939                'uploader_type' => 19, // 19 - Chivox file
8940                'source' => $tempLocation_wav,
8941                'key' => $filename_wav,
8942                'file' => $_FILES['audio_wav'],
8943                'delete_image' => true
8944            ));
8945            $responseGoogle = myTools::performGooglSpeechToText([
8946                'audio_url' => $resultUpload['FileStorage']['url'],
8947                'user_language' => $userLanguage
8948            ]);
8949
8950            // if google speech to text failed, prematurely end
8951            if (!$responseGoogle['success']) {
8952                $this->log('error', __METHOD__ . " -- [NC-9388][Google Translate] Unable convert to text. ");
8953                if (!empty($storedRecordingDetails)) {
8954                    $response = array_merge($response, $storedRecordingDetails);
8955                }
8956                return json_encode($response);
8957            }
8958
8959            $response['filename'] = $filename_wav;
8960            $response['audioFileURL'] = isset($resultUpload['FileStorage']['url']) && $resultUpload['FileStorage']['url'] ? $resultUpload['FileStorage']['url'] : '';
8961            $response['audioFileURLMp3'] = isset($chatboxAudRecordUpload['FileStorage']['url']) && $chatboxAudRecordUpload['FileStorage']['url'] ? $chatboxAudRecordUpload['FileStorage']['url'] : '';
8962            $response['convertedSpeech'] = $responseGoogle['convertedSpeech'] ?? "";
8963            $response['success'] = true;
8964
8965            if (!empty($storedRecordingDetails)) {
8966                $response = array_merge($response, $storedRecordingDetails);
8967            }
8968            return json_encode($response);
8969
8970        } else {
8971            $response['SendChivoxRequest'] = true;
8972            $chivox_pc = isset($this->request->data['chivox_pc']) ? $this->request->data['chivox_pc'] : 0;
8973            $rawRefText = isset($this->request->data['refText']) ? $this->request->data['refText'] : "";
8974            $chivoxSampleRate = Configure::read('chivox.sampleRate');
8975            $sampleRate = isset($this->request->data['sampleRate']) && $this->request->data['sampleRate'] <= $chivoxSampleRate ? $this->request->data['sampleRate'] : $chivoxSampleRate;
8976            $kernel = isset($this->request->data['kernel']) ? $this->request->data['kernel'] : null;
8977            $addParams = isset($this->request->data['addParams']) ? json_decode($this->request->data['addParams'], true) : null;
8978            $reftextKeyword = isset($addParams['refTextAnswerKeyword']) ? $addParams['refTextAnswerKeyword'] : "";
8979            $targetKey = isset($addParams['targetKey']) ? $addParams['targetKey'] : "";
8980            $textbookCategory = isset($this->request->data['textbookCategory']) ? $this->request->data['textbookCategory'] : null;
8981            $newReviewResultFlg = isset($this->request->data['newReviewResultFlg']) ? $this->request->data['newReviewResultFlg'] : 0;
8982            $filename = isset($this->request->data['filename']) && $this->request->data['filename'] ? $this->request->data['filename'] : '';
8983            $audioFileURL = isset($this->request->data['audioFileURL']) && $this->request->data['audioFileURL'] ? $this->request->data['audioFileURL'] : '';
8984            $chivox_type = isset($this->request->data['chivox_type']) ? $this->request->data['chivox_type'] : 1;
8985            $questionName = !empty($this->request->data['question_name']) ? $this->request->data['question_name'] : null;
8986            $audioFileURLMp3 = !empty($this->request->data['audioFileURLMp3']) ? $this->request->data['audioFileURLMp3'] : null;
8987
8988            if ($kernel == 'en.sent.score' || $kernel == 'en.pred.score') {
8989                $rawRefText = json_decode($rawRefText, true);
8990                $refText = $rawRefText[0];
8991            } else {
8992                $refText = $rawRefText;
8993            }
8994            // if aws audio is sucess request chivox thru java server
8995            if ($audioFileURL) {
8996
8997                // get chivox type: eiken for this textbook
8998                $chivoxCountType = 5;
8999
9000                // environment
9001                $ENV = Configure::read('ENVIRONMENT');
9002                $uri = myTools::getChivoxURI($ENV);
9003                $uri = isset($uri['chivox_utility']) ? $uri['chivox_utility'] : "";
9004
9005                $pjIdentifierParams = array(
9006                    "pj_identifier" => Configure::read('chivox.identifiers.textbook_eiken'),
9007                    "user_id" => $userID,
9008                    "env" => $ENV,
9009                    "device_type" => "PC"
9010                );
9011                $pj_identifier = myTools::getChivoxIdentifier($pjIdentifierParams);
9012
9013                if ($kernel == 'en.sent.score' || $kernel == 'en.pred.score') {
9014                    $fields = array(
9015                        'action' => "audio",
9016                        'env' => $ENV,
9017                        'filename' => $filename,
9018                        'refText' => $refText,
9019                        'userRecordedAudioUri' => $audioFileURL,
9020                        'sampleRate' => $sampleRate,
9021                        'user_id' => $userID,
9022                        'pj_identifier' => $pj_identifier
9023                    );
9024
9025                    if ($kernel == 'en.pred.score') {
9026                        $fields['action'] = 'audioParagraph';
9027                    }
9028
9029                } else {
9030                    $fields = array(
9031                        'action' => "audioInterview",
9032                        'env' => $ENV,
9033                        'filename' => $filename,
9034                        'refText' => $refText,
9035                        'userRecordedAudioUri' => $audioFileURL,
9036                        'sampleRate' => $sampleRate,
9037                        'keywords' => $reftextKeyword,
9038                        'kernel' => $kernel,
9039                        'user_id' => $userID,
9040                        'pj_identifier' => $pj_identifier
9041                    );
9042                }
9043
9044                $fields_string = "";
9045
9046                //NJ-36647: add device type
9047                $fields['device_type'] = "PC";
9048
9049                //url-ify the data for the POST
9050                foreach ($fields as $key => $value) {
9051                    $fields_string .= $key . '=' . $value . '&';
9052                }
9053                rtrim($fields_string, '&');
9054
9055                $this->log(__METHOD__ . ' [Initializing CURL] ' . json_encode($fields), 'debug');
9056
9057                $ch = curl_init($uri);
9058                curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
9059                curl_setopt($ch, CURLOPT_HEADER, false);
9060                curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
9061                curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
9062                curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 300); // set to 5 minutes
9063                curl_setopt($ch, CURLOPT_TIMEOUT, 300); // set to 5 minutes
9064                curl_setopt($ch, CURLOPT_POST, count($fields));
9065                curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string);
9066
9067                $result = curl_exec($ch);
9068                curl_close($ch);
9069                $result = json_decode($result, true);
9070
9071                if (isset($result['success']) && $result['success']) {
9072
9073                    $response = $result;
9074                    $response['raw_average_score'] = $result['average_score'];
9075                    $getEquivalent = $this->getTextbookEquivalentScores($uploader_id, $targetKey);
9076                    $response['average_score'] = isset($getEquivalent[$result['average_score']]) ? $getEquivalent[$result['average_score']] : $result['average_score'];
9077                    $response['convertedSpeech'] = $convertedSpeech;
9078                    
9079                    $viewFile = '';
9080                    $viewData = array();
9081                    //NJ-7148
9082                    if ($newReviewResultFlg) {
9083                        $viewData = array(
9084                            'details' => $result['details'],
9085                            'scoreValue' => $result['scoreValue'],
9086                            'average_score' => $response['average_score'],
9087                            'convertedSpeech' => $convertedSpeech,
9088                            'audio_url' => $audioFileURLMp3,
9089                            'chivox_pc' => $chivox_pc,
9090                            'target_key' => $targetKey,
9091                            'chivox_type' => $chivox_type,
9092                            'review_flg' => 1
9093                        );
9094                        $viewFile = 'eiken_mock_interview_review_result';
9095                    } else {
9096                        if ($kernel == 'en.sent.score' || $kernel == 'en.pred.score') {
9097                            $viewData = array(
9098                                'details' => $result['details'],
9099                                'scoreValue' => $result['scoreValue'],
9100                                'refText' => $refText,
9101                                'audio_url' => $audioFileURLMp3,
9102                                'average_score' => $response['average_score'],
9103                                'chivox_type' => $chivox_type,
9104                                'target_key' => $targetKey
9105                            );
9106                            $viewFile = 'reading_ai_onlesson_result';
9107                        } else {
9108                            $viewData = array(
9109                                'average_score' => $response['average_score'],
9110                                'target_key' => $targetKey,
9111                                'audio_url' => $audioFileURLMp3,
9112                                'convertedSpeech' => $convertedSpeech,
9113                                'chivox_type' => 0 // - force set to 0 for display purposes
9114                            );
9115
9116                            $response['resultView'] = $convertedSpeech;
9117                        }
9118                    }
9119
9120                    if (!empty($viewData)) {
9121                        // instantiate view
9122                        $view = new View($this, false);
9123                        $view->layout = false;
9124
9125                        // - old design
9126                        if (!empty($viewFile)) {
9127                            $response['resultViewOld'] = $view->element($viewFile, $viewData);
9128                        }
9129
9130                        $resultViewStudent = $view->element('student_chat_class_chivox', $viewData);
9131                        $resultViewTeacher = $view->element('teacher_chat_class_chivox', $viewData);
9132                        
9133                        $response['resultViewStudent'] = $resultViewStudent;
9134                        $response['resultViewTeacher'] = $resultViewTeacher;
9135                    }    
9136                    
9137                    $this->log("file merge async rsponse: " . json_encode($response));
9138                } else {
9139                    $response['error'] = !empty($result['error']) ? $result['error'] : "Chivox Error!";
9140                    // report chivox error to slack.
9141                    $this->sendChivoxSlack($result);
9142                }
9143
9144                // return chivox type for app
9145                $params = array(
9146                    "pj_identifier" => $pj_identifier,
9147                    "user_id" => $userID,
9148                    "env" => $ENV
9149                );
9150                $getChivoxIdentifier = myTools::getChivoxIdentifier($params);
9151
9152                // get query_date
9153                $currentDay = date('Y-m-d');
9154                $queryCountToSave = array(
9155                    'user_id' => $userID,
9156                    'query_date' => $currentDay,
9157                    'chivox_type' => $chivoxCountType,
9158                    'chivox_callan_pp_id' => null,
9159                    'chivox_monthly_test_id' => null,
9160                    'chivox_textbook_category' => $textbookCategory,
9161                    'question_count' => 1,
9162                    'chivox_identifier' => $getChivoxIdentifier
9163                );
9164                $this->UsersChivoxCountQuery->updateQuestionQueryCount($queryCountToSave);
9165
9166                $this->loadModel('FileStorage');
9167                // Delete aws
9168                $del = $this->FileStorage->removeFile(array("url" => $audioFileURL));
9169                if (!$del) {
9170                    $this->log('[NC-8372 uploadRecordedFileChivox Fail deleting the audio from File storage, AWS. ' . json_encode($audioFileURL), 'debug');
9171                }
9172            }
9173            // NC-9388: END CHIVOX PR0CESS
9174            return json_encode($response);
9175        }
9176    }
9177
9178    /**
9179     * @api {post} /teacher/api/getTimezoneUserAndTeacher getTimezoneUserAndTeacher()
9180     * @apiName getTimezoneUserAndTeacher
9181     * @apiGroup API
9182     * @apiDescription Get timezone
9183     * @apiSampleRequest off
9184     * 
9185     * @apiBody{String} chatHash The chat hash.
9186     * @apiBody{String} user_id The user ID.
9187     * 
9188     * @apiSuccess {Boolean} status The status of the request.
9189     * @apiSuccess {Array} data The data of the request. (`$this->Timezone->getTimezoneUserAndTeacher()`)
9190     * 
9191     * @apiErrorExample {json} Success-Response:
9192     *     {
9193     *       "status": true,
9194     *       "data": {...}
9195     *     }
9196     *
9197     * @apiErrorExample {js} Used in: Elements
9198     * Location: "view/Elements/chat_area_js.php"
9199     */
9200        /**
9201     * Get timezone
9202     * @return mixed - json_encoded array
9203     */
9204    public function getTimezoneUserAndTeacher() {
9205        $this->layout = '';
9206        $this->autoRender = false;
9207        $chatHash = $this->request['data']['chatHash'];
9208        $lessonOnAir = $this->LessonOnair->find('first',[
9209            'conditions' => [
9210                'LessonOnair.chat_hash' => $chatHash
9211            ],
9212            'recursive' => -1 
9213        ]);
9214        $teacherId = $this->Auth->user('id');
9215        $response = $this->Timezone->getTimezoneUserAndTeacher($teacherId, $lessonOnAir['LessonOnair']['user_id']);
9216        
9217        return json_encode([
9218            'status' => true,
9219            'data' => $response
9220        ]);
9221    }
9222
9223    /**
9224     * @api {post} /teacher/api/uploadRecordedFileAsync uploadRecordedFileAsync()
9225     * @apiName uploadRecordedFileAsync
9226     * @apiGroup API
9227     * @apiDescription Save audio and get equivalent text from Google API Asyncronous recognition
9228     * @apiSampleRequest off
9229     * 
9230     * @apiBody {File} audio The audio file to upload.
9231     * @apiBody {String} connect_id The connect ID.
9232     * @apiBody {String} store_audio_record The store audio record.
9233     * @apiBody {String} user_id The user ID.
9234     * 
9235     * @apiSuccess {Boolean} success The status of the request.
9236     * @apiSuccess {String} convertedSpeech The converted text.
9237     * @apiSuccess {Array} config The configuration.
9238     * @apiSuccess {String} audio_url The URL of the audio file.
9239     * @apiSuccess {String} textbook_name The textbook name.
9240     * 
9241     * @apiErrorExample {json} Success-Response:
9242     *     {
9243     *       "success": true,
9244     *       "convertedSpeech": "Converted text",
9245     *       "config": [],
9246     *       "audio_url": "url",
9247     *       "textbook_name": "Category Subcategory : Textbook"
9248     *     }
9249     *
9250     * @apiErrorExample {json} Error-Response:
9251     *     {
9252     *       "success": false,
9253     *       "convertedSpeech": null,
9254     *       "config": []
9255     *     }
9256     */
9257    /**
9258    * Save audio and get equivalent text from Google API Asyncronous recognition
9259    * @return mixed - json $response
9260    */
9261    public function uploadRecordedFileAsync() {
9262        $this->autoRender = false;
9263        $textbookNameHolder = $audioUrl = "";
9264        $response = array(
9265            'success' => false,
9266            'convertedSpeech' => '{"jpn" : "〔エラー〕:音声認識に問題があります。ヘッドセットあるいはイヤホンの着用/インターネット環境のご確認をお願いします","eng" : "Error : There is a problem with voice recognition. Please wear a headset or earphones and check your internet environment"}',
9267            'config' => array()
9268        );
9269
9270        $uploader_id = $this->Auth->user('id');
9271        if(empty($uploader_id) || !$uploader_id){
9272            return json_encode($response);
9273        }
9274        // check if request has a valid file
9275        if ((!isset($_FILES['audio_wav']['size']) || !$_FILES['audio_wav']['size']) || (!isset($_FILES['audio_mp3']['size']) || !$_FILES['audio_mp3']['size'])) {
9276            return json_encode($response);
9277        }
9278
9279        $connectId                 = !empty($this->request->data['connect_id']) ? $this->request->data['connect_id'] : null;
9280        $storedRecordingFlag     = !empty($this->request->data['store_audio_record']) ? true : false;
9281        $userId                    = !empty($this->request->data['user_id']) ? $this->request->data['user_id'] : null;
9282        $questionName            = !empty($this->request->data['question_name']) ? $this->request->data['question_name'] : null;
9283        $questionTitle            = !empty($this->request->data['question_title']) ? $this->request->data['question_title'] : null;
9284        $isSpeakTrainIelts        = !empty($this->request->data['isSpeakTrainIelts']) ? $this->request->data['isSpeakTrainIelts'] : false;
9285
9286        // - override question name
9287        if (!empty($questionTitle)) {
9288            $questionName = $questionTitle;
9289        }
9290        
9291        // Get audio temporary folder
9292        $audioContainer = ROOT . '/instructor/webroot/sound';
9293        if (!is_dir($audioContainer)) {
9294            mkdir($audioContainer);
9295        }
9296
9297        // move the file from temp name to local folder using $output name
9298        $wavInput = $_FILES['audio_wav']['tmp_name'];
9299        $mp3Input = $_FILES['audio_mp3']['tmp_name'];
9300
9301        $fileNameWAV = $uploader_id . $_FILES['audio']['name'] . '.wav';
9302        $fileNameMP3 = $uploader_id . $_FILES['audio']['name'] . '.mp3';
9303
9304        $wavOutput = $audioContainer . '/' . $fileNameWAV;
9305        $mp3Output = $audioContainer . '/' . $fileNameMP3;
9306
9307        // delete audio file if already exist
9308        if (file_exists($wavOutput)) {
9309            unlink($wavOutput);
9310        }
9311        if (file_exists($mp3Output)) {
9312            unlink($mp3Output);
9313        }
9314        // upload local file
9315        $moveFileStatusWav = move_uploaded_file($wavInput, $wavOutput);
9316        $moveFileStatusMp3 = move_uploaded_file($mp3Input, $mp3Output);
9317        //-- Move audio recordings to s3 
9318        $this->loadModel('FileStorage');
9319        if ($moveFileStatusWav && $moveFileStatusMp3 && !empty($storedRecordingFlag)) {
9320            $wavFilename = time() . uniqid() . '_wav_' . basename($wavOutput);
9321            $mp3Filename = time() . uniqid() . '_mp3_' . basename($mp3Output);
9322            $wavResult = $this->FileStorage->uploadFile([
9323                'uploader_id'  => $this->Auth->user('id'),
9324                'uploader_type' => 34,
9325                'source'       => $wavOutput,
9326                'key'          => $wavFilename,
9327                'file'         => $_FILES['audio_wav'],
9328                'delete_image' => true
9329            ]);
9330            
9331            $mp3Result = $this->FileStorage->uploadFile([
9332                'uploader_id'  => $this->Auth->user('id'),
9333                'uploader_type' => 34,
9334                'source'       => $mp3Output,
9335                'key'          => $mp3Filename,
9336                'file'         => $_FILES['audio_mp3'],
9337                'delete_image' => true
9338            ]);
9339            $fileUrl = $mp3Result['FileStorage']['url'];
9340            if (!empty($fileUrl)) {
9341                $response['audio_url'] = str_replace('https://', '', $fileUrl);
9342            }
9343        }
9344
9345        // query speech to text
9346        if ($response['audio_url'] && !$isSpeakTrainIelts) {
9347            
9348            // - debug lang ni. remove ni siya.
9349            $responseGoogle = myTools::performGooglSpeechToText([
9350                'audio_url' => $wavResult['FileStorage']['url'],
9351            ]);
9352        
9353            // - save log
9354            if (isset($responseGoogle['totalTime']) && !empty($responseGoogle['totalTime'])) {
9355            
9356                $response['convertedSpeech'] = $responseGoogle['convertedSpeech'] ?? '';
9357                $sum = str_replace('s', '', $responseGoogle['totalTime']);
9358
9359                //save speech to text logs
9360                $logParams = array(
9361                    'service' => 2,
9362                    'type' => 3,
9363                    'controller' => static::class,
9364                    'method' => __METHOD__,
9365                    'url' => $this->request->here(),
9366                    'value'    => number_format($sum, 2 , '.', '')
9367                );
9368                $googleLogs = ClassRegistry::init('GoogleTranslateLog');
9369                $result = $googleLogs->saveLog($logParams);        
9370                //check if logs saved
9371                if(!$result){
9372                    $this->log('error', __METHOD__." -- [Google Translate] Unable to save logs. ");
9373                }
9374            }
9375        }
9376
9377        if ($isSpeakTrainIelts) {
9378            $response['convertedSpeech'] = '';
9379        }
9380
9381        //-- user current language
9382        $userLanguage = $this->User->fetchUserCurrentLanguage(array(
9383            'user_id' => $userId
9384        ));
9385        $textbookName = $this->TextbookConnect->getTextbookName(array(
9386            'connect_id' => $connectId,
9387            'native_language' => $userLanguage,
9388            'translate_other_lang' => true
9389        ));
9390        $response['textbook_name'] .= !empty($textbookName['textbook_category_name']) ? $textbookName['textbook_category_name'] : '';
9391        $response['textbook_name'] .= !empty($textbookName['textbook_subcategory_name']) ? ' ' . $textbookName['textbook_subcategory_name'] : '';
9392        $response['textbook_name'] .= !empty($textbookName['textbook_name']) ? ' : ' . $textbookName['textbook_name'] : '';
9393
9394        // instantiate view
9395        $view = new View($this, false);
9396        $view->layout = false;
9397
9398        $viewData = array(
9399            'convertedSpeech' => $response['convertedSpeech'],
9400            'audio_url' => $fileUrl,
9401            'isSpeechRecording' => 1,
9402            'question_name' => !empty($questionName) ? $questionName : $textbookName['textbook_name']
9403        ); 
9404
9405        $resultViewStudent = $view->element('student_chat_class_chivox', $viewData);
9406        $viewData['question_name'] = !empty($questionName) ? $questionName : $textbookName['textbook_name_eng'];
9407        $resultViewTeacher = $view->element('teacher_chat_class_chivox', $viewData);
9408
9409        $response['resultViewStudent'] = $resultViewStudent;
9410        $response['resultViewTeacher'] = $resultViewTeacher;
9411
9412        $response['success'] = true;
9413
9414        // - delete audio file if already exist
9415        if (file_exists($output)) {
9416            unlink($output);
9417        }
9418        
9419        return json_encode($response);
9420    }
9421    
9422    /**
9423     * @api {get} /teacher/api/debugSocketLog debugSocketLog()
9424     * @apiName debugSocketLog
9425     * @apiGroup API
9426     * @apiDescription Debug socket log
9427     * @apiSampleRequest off
9428     * 
9429     * @apiBody {String} chat_hash The chat hash.
9430     * @apiBody {String} teacher_id The teacher ID.
9431     * @apiBody {String} user_id The user ID.
9432     * @apiBody {String} temp_id The temporary ID.
9433     * @apiBody {Object} data The debug data.
9434     * 
9435     * @apiSuccess {Boolean} success The status of the request.
9436     *
9437     * @apiErrorExample {json} Success-Response:
9438     *     {
9439     *       "success": true
9440     *     }
9441     *
9442     * @apiErrorExample {js} Used in: Elements
9443     * Location: "view/Elements/chat_area_js.php"
9444     * 
9445     * @apiErrorExample {js} Used in: View
9446     * Location: "view/Elements/HtmlTextBook.php"
9447     * 
9448      * @apiErrorExample {js} Used in: AngularJS
9449      * Location: "webroot/js/ng/app.js"
9450     * 
9451     * @apiErrorExample {js} Used in: WebRTC
9452      * Location: "webroot/js/webrtcv2/connect.js"
9453      * Location: "webroot/js/webrtcv2/connectEnvModal.js"
9454     */
9455    public function debugSocketLog($chat_hash = "", $teacher_id = "", $user_id = "", $temp_id = ""){
9456        $this->autoRender = false;
9457
9458        // - get data
9459        $data = json_decode($this->request->input(),true);
9460        
9461        // - set slack message
9462        $mySlack = new mySlack();
9463        $mySlack->token = "xoxb-392902820692-6499139810404-RHHGc6Z5ZB9o2KkiGhzTTtlQ";
9464        $mySlack->username = "Bad Teacher Connection Debug ";
9465        $mySlack->text = "";
9466        $mySlack->channel = myTools::checkChannel("#bad-teacher-connections", "#bad-teacher-connections-dev");
9467
9468        if($teacher_id && $user_id) {
9469            // Get track log number
9470            $lessonNumber = ClassRegistry::init('LessonTrackLog')->find('first', array(
9471                'fields' => 'LessonTrackLog.lesson_number',
9472                'conditions' => ['LessonTrackLog.chat_hash' => $chat_hash],
9473                'order' => ['LessonTrackLog.id' => 'DESC']
9474            ));
9475        
9476            if (empty($lessonNumber)) {
9477                // Create track log if not found
9478                $trackParam = [
9479                    'flag' => 'create',
9480                    'data' => [
9481                        'chat_hash' => $chat_hash,
9482                        'user_id' => $user_id,
9483                        'teacher_id' => $teacher_id
9484                    ]
9485                ];
9486        
9487                ClassRegistry::init('LessonTrackLog')->processLog($trackParam);
9488        
9489                // Get track log number after creation
9490                $lessonNumber = ClassRegistry::init('LessonTrackLog')->find('first', array(
9491                    'fields' => 'LessonTrackLog.lesson_number',
9492                    'conditions' => ['LessonTrackLog.chat_hash' => $chat_hash],
9493                    'order' => ['LessonTrackLog.id' => 'DESC']
9494                ));
9495            }
9496
9497            $userId = Configure::read('ENVIRONMENT') === "PRODUCTION" ? '<@U0410PF1WUT> <@U01S0E3Q9HQ> <@U05V885BMNK>' : '<@U0410PF1WUT> <@U01S0E3Q9HQ> <@U05V885BMNK>';
9498
9499            $mySlack->link_names = true;
9500            $mySlack->text = $userId . "\n";
9501            $mySlack->text .= "```";
9502            $mySlack->text .= "Error" . "\n";
9503            $mySlack->text .= $data["message"] . "\n";
9504            $mySlack->text .= "Date = " . date('Y-m-d H:i:s') . "\n";
9505            $mySlack->text .= "Lesson ID = " . json_encode($lessonNumber['LessonTrackLog']['lesson_number']) . "\n";
9506            $mySlack->text .= "Teacher ID = " . $teacher_id . "\n";
9507            $mySlack->text .= "Temp ID = " . $temp_id . "\n";
9508            $mySlack->text .= "Socket Log = " . $data["socket_log"] . "\n";
9509
9510        } else {
9511            $mySlack->text .= "```";
9512            $mySlack->text .= "chat_hash: " . $chat_hash . "\n";
9513            $mySlack->text .= "content: " . isset($data["message"]) ? $data["message"] : '-';
9514
9515            // - if has user_type
9516            if (isset($data['user_type']) && $data['user_type']) {
9517                $mySlack->text .= "\n";
9518                $mySlack->text .= "user: " . isset($data["user_type"]) ? $data["user_type"] : '-';
9519            }
9520            
9521        }
9522
9523        // - end slack message
9524        $mySlack->text .= "```";
9525        
9526        // - send slack message
9527        $test = $mySlack->postMessage();
9528        die();
9529    }
9530    //unused function, 
9531    public function logTeacherDebugError() {
9532        $this->autoRender = false;
9533        $response = array('status' => 'error', 'message' => 'An error occurred');
9534
9535        if ($this->request->is('ajax')) {
9536            $error = $this->request->input('json_decode', true)['error'] ?? 'Unknown error';
9537
9538            try {
9539                $this->sendSlackDebugMessage($error);
9540                $response = array('status' => 'success', 'message' => 'Error logged and reported');
9541            } catch (Exception $e) {
9542                $response['message'] = 'Failed to report error: ' . $e->getMessage();
9543            }
9544        }
9545
9546        return json_encode($response);
9547    }
9548
9549    private function sendSlackDebugMessage($error) {
9550        // - set slack message
9551        $mySlack = new mySlack();
9552        $mySlack->token = "xoxb-392902820692-6499139810404-RHHGc6Z5ZB9o2KkiGhzTTtlQ";
9553        $mySlack->username = "Teacher Debug Logger";
9554        $mySlack->channel = myTools::checkChannel("#nc-teacher-errors", "#fdc-test-channel-debug");
9555
9556        $referrerDebug = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : 'No referrer set';
9557        $urlDebug = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : 'No URI set';
9558
9559        $mySlack->text = "";
9560        $mySlack->text .= "```";
9561        $mySlack->text .= "Error: " . json_encode($error);
9562        $mySlack->text .= "\nTimestamp: " . date('Y-m-d H:i:s');
9563        $mySlack->text .= " \nreferrer: " . json_encode($referrerDebug);
9564        $mySlack->text .= " \nurl: " . json_encode($urlDebug);
9565        $mySlack->text .= "```";
9566        $test = $mySlack->postMessage();        
9567    }
9568
9569    /**
9570     * @api {post} /teacher/api/saveSkywayDebug saveSkywayDebug()
9571     * @apiName saveSkywayDebug
9572     * @apiGroup API
9573     * @apiDescription Save skyway debug
9574     * @apiSampleRequest off
9575     * 
9576     * @apiBody {String} chat_hash The chat hash.
9577     * @apiBody {String} message The debug message.
9578     * 
9579     * @apiSuccess {Boolean} success The status of the request.
9580     *
9581     * @apiErrorExample {json} Success-Response:
9582     *     {
9583     *       "success": true
9584     *     }
9585     *
9586     * @apiErrorExample {json} Error-Response:
9587     *     {
9588     *       "success": false
9589     *     }
9590     * 
9591     * @apiErrorExample {js} Used in: WebRTC
9592      * Location: "webroot/js/webrtcv2/event.common.js"
9593
9594     * @apiErrorExample {js} Used in: JS
9595      * Location: "webroot/js/socket.debug.js"
9596     */
9597    public function saveSkywayDebug($chat_hash = ""){
9598        // - if no chathash
9599        if (!$chat_hash) {
9600            return false;
9601        }
9602        
9603        // - data
9604        $data = json_decode($this->request->input(),true);
9605
9606        // - save login history
9607        $this->TeachersDebugSkyway->clear();
9608        $this->TeachersDebugSkyway->create();
9609        $this->TeachersDebugSkyway->set(array(
9610            'chat_hash' => $chat_hash,
9611            'message' => $data["message"],
9612        ));
9613        $this->TeachersDebugSkyway->save();
9614        die();
9615    }
9616
9617    /**
9618     * @api {post} /teacher/api/reCheckReservedLesson reCheckReservedLesson()
9619     * @apiName reCheckReservedLesson
9620     * @apiGroup API
9621     * @apiDescription checking reserved lesson before changing teacher status & logout
9622     * @apiSampleRequest off
9623     * 
9624     * @apiSuccess {Boolean} is_reserved The status of the request.
9625     * 
9626     * @apiErrorExample {json} Success-Response:
9627     *     {
9628     *       "is_reserved": true
9629     *     }
9630     *
9631      * @apiErrorExample {js} Used in: AngularJS
9632      * Location: "webroot/js/ng/controller/header.js"
9633     */
9634    /**
9635     * checking reserved lesson before changing teacher status & logout
9636     * @param mixed - teacherId
9637     */
9638    public function reCheckReservedLesson() {
9639        $this->autoRender = false;
9640        $isReserved = false;
9641        $teacherId = $this->Auth->user('id');
9642
9643        if ($teacherId && $this->LessonSchedule->checkSubTeacherLessonNow($teacherId)) {
9644            $isReserved = true;
9645        }
9646
9647        return json_encode(array('is_reserved' => $isReserved));
9648    }
9649
9650    /**
9651     * @api {post} /teacher/api/virtualCameraAllow virtualCameraAllow()
9652     * @apiName virtualCameraAllow
9653     * @apiGroup API
9654     * @apiDescription Get virtual camera allow
9655     * @apiSampleRequest off
9656     * 
9657     * @apiSuccess {Array} result The result of the request.
9658     * 
9659     * @apiErrorExample {json} Success-Response:
9660     *     {
9661     *       "0": "Character1",
9662     *       "1": "Character2",
9663     *       ...
9664     *     }
9665     *
9666     * @apiErrorExample {js} Used in: Elements
9667     * Location: "view/Elements/header.php"
9668     * 
9669     * @apiErrorExample {js} Used in: View
9670     * Location: "view/Elements/index.php"
9671     */
9672    public function virtualCameraAllow() {
9673        $this->autoRender = false;
9674        $result = array(); // set default as empty
9675
9676        $this->CharacterSettings->openDBReplica();
9677        $characterSettings = $this->CharacterSettings->find('all', array(
9678            'fields' => array(
9679                'CharacterSettings.name'
9680            ),
9681            'recursive' => -1
9682        ));
9683        $this->CharacterSettings->closeDBReplica();
9684        if (isset($characterSettings) && is_array($characterSettings)) {
9685            foreach($characterSettings as $key => $value){
9686                $result[$key] = $value['CharacterSettings']['name'];
9687            }
9688        }
9689
9690        return json_encode($result);
9691    }
9692
9693    private function getTextbookEquivalentScores($uploader_id, $key) {
9694        // get subcategory of current textboook used
9695        if ($key == 'narrate') {
9696            $key = 'read';
9697        }
9698        $onAir = $this->LessonOnair->find('first', array(
9699            'fields' => array('TextbookConnect.subcategory_id'),
9700            'joins' => array(
9701                array(
9702                    'type' => 'LEFT',
9703                    'table' => 'textbook_connects',
9704                    'alias' => 'TextbookConnect',
9705                    'conditions' => 'TextbookConnect.id = LessonOnair.connect_id'
9706                )
9707            ),
9708            'conditions' => array('LessonOnair.teacher_id' => $uploader_id),
9709            'recursive' => -1
9710        ));
9711        $subCategoryId = isset($onAir['TextbookConnect']['subcategory_id']) && !empty($onAir['TextbookConnect']['subcategory_id']) ? $onAir['TextbookConnect']['subcategory_id'] : Configure::read('dynamic_textbook_subcategory.eiken_grade_3');
9712        $dynamicTextbookSeries = Configure::read('dynamic_textbook_subcategory');
9713        $dynamicTextbookCourses = Configure::read('dynamic_textbook_subcategory_course');
9714        if (in_array($subCategoryId, $dynamicTextbookCourses)){
9715            // get series equivalent
9716            $subCategoryId =  $dynamicTextbookSeries[array_search($subCategoryId,$dynamicTextbookCourses)];
9717        }
9718        // get memcached data
9719        if (!class_exists('myMemcached',false)) { App::uses('myMemcached', 'Lib'); }
9720        $memcached = new myMemcached();
9721        $getMemKey = 'textbook_equivalent_score_'.$subCategoryId;
9722        $rawEquivalentScores = $memcached->get($getMemKey);
9723
9724        // fetch from DB if memcached does not exist
9725        if (!$rawEquivalentScores){
9726            $this->loadModel('ChivoxTextbookEquivalentScore');
9727            $result = $this->ChivoxTextbookEquivalentScore->find('first', array(
9728                'fields' => array('equivalent_scores'),
9729                'conditions' => array(
9730                    "textbook_subcategory_id" => $subCategoryId
9731                )
9732            ));
9733            $rawEquivalentScores = isset($result['ChivoxTextbookEquivalentScore']['equivalent_scores']) && !empty($result['ChivoxTextbookEquivalentScore']['equivalent_scores']) ? $result['ChivoxTextbookEquivalentScore']['equivalent_scores'] : "";
9734            if (!empty($rawEquivalentScores)) {
9735                $memcached->set(array(
9736                    'key' => $getMemKey,
9737                    'value' => $rawEquivalentScores,
9738                    'expire' => 1209600 // 2 weeks
9739                ));
9740            }
9741        }
9742
9743        $rawScore = json_decode($rawEquivalentScores, true);
9744        $getTargeyKey = isset($rawScore[$key]) ? $rawScore[$key] : array();
9745
9746        return $getTargeyKey;
9747    }
9748
9749    /**
9750     * @api {post} /teacher/api/confirmSuddenLesson confirmSuddenLesson()
9751     * @apiName confirmSuddenLesson
9752     * @apiGroup API
9753     * @apiDescription Confirm sudden lesson
9754     * @apiSampleRequest off
9755     * 
9756     * @apiBody {String} teacher_id The teacher ID.
9757     * 
9758     * @apiSuccess {Array} Teacher The teacher data.
9759     * @apiSuccess {String} Teacher.id The teacher ID.
9760     * @apiSuccess {String} Teacher.rank_coin_id The rank coin ID.
9761     * @apiSuccess {String} Teacher.current_rank_id The current rank ID.
9762     * @apiSuccess {String} Teacher.reserve_coin_data_id The reserve coin data ID.
9763     * @apiSuccess {String} Teacher.memo The memo.
9764     * 
9765     * @apiErrorExample {json} Success-Response:
9766     *     {
9767     *       "Teacher": {
9768     *           "id": 1,
9769     *           "rank_coin_id": 19,
9770     *           "current_rank_id": 2,
9771     *           "reserve_coin_data_id": 3,
9772     *           "memo": "2023/10/01\nTutor Category 22→19 because teacher requested for \"Sudden Class\"\n\nOld memo"
9773     *       }
9774     *     }
9775     *
9776      * @apiErrorExample {js} Used in: AngularJS
9777      * Location: "webroot/js/ng/controller/header.js"
9778     */
9779    public function confirmSuddenLesson() {
9780        $this->autoRender = false;
9781        if ($this->request->is('post')) {
9782            $teacherID = $this->request->data['teacher_id'];
9783
9784            if ($teacherID) {
9785                $this->Teacher->openDBReplica();
9786                $teacherData = $this->Teacher->find('first', array(
9787                    'fields' => array(
9788                        'Teacher.rank_coin_id',
9789                        'Teacher.current_rank_id',
9790                        'Teacher.reserve_coin_data_id',
9791                        'Teacher.memo'
9792                    ),
9793                    'conditions' => array(
9794                        'Teacher.id' => $teacherID
9795                    ),
9796                    'recursive' => -1
9797                ));
9798                $this->Teacher->closeDBReplica();
9799
9800                if ($teacherData) {
9801                    $updateData = array();
9802                    $currentDate = date('Y/m/d');
9803                    $teacherRankCoinID = $teacherData['Teacher']['rank_coin_id'];
9804                    $reserveCoinDataID = $teacherData['Teacher']['reserve_coin_data_id'];
9805                    $oldMeno = $teacherData['Teacher']['memo'];
9806
9807                    // check if teacher is allowed to apply sudden lesson
9808                    $allowedApplySuddenLesson = Configure::read('allowed_apply_sudden_lesson');
9809                    if (!in_array($teacherRankCoinID, $allowedApplySuddenLesson)) {
9810                        return;
9811                    }
9812
9813                    // change rank_coin_id
9814                    if ($teacherRankCoinID == 22) {
9815                        $updateData['rank_coin_id'] = 19;
9816                    } else if ($teacherRankCoinID == 58) {
9817                        $updateData['rank_coin_id'] = 57;
9818                    } else if ($teacherRankCoinID == 72) {
9819                        $updateData['rank_coin_id'] = 73;
9820                    }
9821
9822                    // change current_rank_id
9823                    $this->TeacherRankCoin->openDBReplica();
9824                    $currentRankID = $this->TeacherRankCoin->find('first', array(
9825                        'fields' => array('TeacherRankCoin.tutor_default_rank'),
9826                        'conditions' => array('TeacherRankCoin.id' => $updateData['rank_coin_id']),
9827                        'recursive' => -1
9828                    ));
9829                    $this->TeacherRankCoin->closeDBReplica();
9830                    $updateData['current_rank_id'] = $currentRankID ? $currentRankID['TeacherRankCoin']['tutor_default_rank'] : null;
9831
9832                    // change reserve_coin_data_id
9833                    $reserveCoinDataArray = $this->HomeBasedReserveCoinData->getCoinDataList($updateData['rank_coin_id'], $updateData['current_rank_id']);
9834                    if ($reserveCoinDataArray) {
9835                        $ids = array_column($reserveCoinDataArray, 'id');
9836                        if (empty($reserveCoinDataID) || !in_array($reserveCoinDataID, $ids)) {
9837                            $updateData['reserve_coin_data_id'] = end($ids);
9838                        } else {
9839                            $updateData['reserve_coin_data_id'] = $reserveCoinDataID;
9840                        }
9841                    } else {
9842                        $updateData['reserve_coin_data_id'] = null;
9843                    }
9844
9845                    // add memo
9846                    $updateData['memo'] = $currentDate."\nTutor Category ".$teacherRankCoinID.'→'.$updateData['rank_coin_id'].' because teacher requested for "Sudden Class"'."\n\n".$oldMeno;
9847
9848                    // when changing teacher rank_coin_id
9849                    if ((int)$teacherData['Teacher']['rank_coin_id'] !== (int)$updateData['rank_coin_id']) {
9850                        $hbfsParams = array(
9851                            'teacherId' => $teacherID,
9852                            'periodDate' => myTools::getCutoffDate(date('Y-m-d', strtotime('now'))),
9853                            'rankCoinSetting' => $this->TeacherRankCoin->getRankCoinSettings($updateData['rank_coin_id']),
9854                            'newRankCoin' => $updateData['rank_coin_id'],
9855                            'wuBankDetailTypeChange' => false,
9856                            'updateFinalBankDetailType' => null
9857                        );
9858                        $this->HomeBasedFinalSalaryData->teacherChangeRankCoinAndBankDetailType($hbfsParams);
9859                    }
9860
9861                    // when changing teacher current_rank_id
9862                    if ((int)$teacherData['Teacher']['current_rank_id'] !== (int)$updateData['current_rank_id']) {
9863                        $cutoffPeriod = myTools::hbGetCutoffDates($currentDate);
9864
9865                        $this->HomeBasedRankBasicAmount->openDBReplica();
9866                        $getRankAmountId = $this->HomeBasedRankBasicAmount->find('first', array(
9867                            'fields' => array(
9868                                'HomeBasedRankBasicAmount.id'
9869                            ),
9870                            'conditions' => array(
9871                                'HomeBasedRankBasicAmount.rank_id' => $updateData['current_rank_id']
9872                            ),
9873                            'recursive' => -1
9874                        ));
9875                        $this->HomeBasedRankBasicAmount->closeDBReplica();
9876
9877                        if ($getRankAmountId) {
9878                            $teacherRankDataLog = array(
9879                                'basic_amount_log_id' => $getRankAmountId['HomeBasedRankBasicAmount']['id'],
9880                                'teacher_id' => $teacherID,
9881                                'rank_id' => $updateData['current_rank_id'],
9882                                'period_date' => myTools::getCutoffDate($cutoffPeriod['periodDate'])
9883                            );
9884
9885                            $this->HomeBasedTeacherRankLog->clear();
9886                            $this->HomeBasedTeacherRankLog->create();
9887                            $this->HomeBasedTeacherRankLog->set($teacherRankDataLog);
9888                            $this->HomeBasedTeacherRankLog->save();
9889                        }
9890
9891                    }
9892
9893                    // when changing teacher reserve_coin_data_id
9894                    if((int)$teacherData['Teacher']['reserve_coin_data_id'] !== (int)$updateData['reserve_coin_data_id']){
9895                        $teacherSettings = $this->Teacher->getReserveCoinData($teacherID);
9896                        $teacherSettings['Teacher']['rank_coin_id'] = $updateData['rank_coin_id'];
9897                        $teacherSettings['Teacher']['current_rank_id'] = $updateData['current_rank_id'];
9898                        $newReserveCoinId = $updateData['reserve_coin_data_id'];
9899
9900                        $this->Teacher->setReserveCoinData($teacherSettings, $newReserveCoinId, true, false);
9901                    }
9902
9903                    $this->Teacher->clear();
9904                    $this->Teacher->read(array_keys($updateData), $teacherID);
9905                    $this->Teacher->set($updateData);
9906                    $updatedTeacher = $this->Teacher->save();
9907                    
9908                    if ($updatedTeacher) {
9909                        return json_encode($updatedTeacher);
9910                    }
9911                }
9912            }
9913        } else {
9914            return;
9915        }
9916    }
9917    
9918    /**
9919     * @api {post} /teacher/api/postEventCampaign postEventCampaign()
9920     * @apiName postEventCampaign
9921     * @apiGroup API
9922     * @apiDescription Post event campaign
9923     * @apiSampleRequest off
9924     * 
9925     * @apiBody {String} event_campaign_id The event campaign ID.
9926     * @apiBody {String} voted_name The voted name.
9927     * @apiBody {String} teacher_id The teacher ID.
9928     * 
9929     * @apiSuccess {Boolean} error The status of the request.
9930     * @apiSuccess {String} msg The message.
9931     * 
9932     * @apiErrorExample {json} Success-Response:
9933     *     {
9934     *       "error": false
9935     *     }
9936     *
9937     * @apiErrorExample {json} Error-Response:
9938     *     {
9939     *       "error": true,
9940     *       "msg": "Teacher already voted."
9941     *     }
9942     * 
9943      * @apiErrorExample {js} Used in: AngularJS
9944      * Location: "webroot/js/ng/app.js"
9945     */
9946    //NJ-11612
9947    public function postEventCampaign() {
9948        $res = array('error' => true);
9949        $this->autoRender = false;
9950
9951        if ($this->request->is('ajax')) {
9952            $postData = $this->request->data;
9953            if (
9954                (!isset($postData['event_campaign_id']) || !$postData['event_campaign_id'])
9955                || (!isset($postData['voted_name']) || !$postData['voted_name'])
9956                || (!isset($postData['teacher_id']) || !$postData['teacher_id'])
9957            ) {
9958                return json_encode($res);
9959            }
9960
9961            //check if teacher is exists
9962            $this->Teacher->recursive = -1;
9963            $teacherData = $this->Teacher->findById($postData['teacher_id']);
9964            if ($teacherData) {
9965                $eventPostData = array(
9966                    'event_campaign_id' => $postData['event_campaign_id'],
9967                    'teacher_id' => $this->Auth->User('id'),
9968                    'status' => 1,
9969                    'question1' => 'Teacher',
9970                    'question2' => $postData['voted_name']
9971                );
9972
9973                $eventCampaign = ClassRegistry::init('EventCampaign');
9974                //Check teacher if already posted/voted
9975                $userEventPosts = $eventCampaign->countEventPostsPerUser(
9976                    array(
9977                        'event_campaign_id' => $postData['event_campaign_id'],
9978                        'teacher_id' => $postData['teacher_id'],
9979                        'status' => 1
9980                    )
9981                );
9982                if($userEventPosts) {
9983                    $res['msg'] = 'Teacher already voted.';
9984                    return json_encode($res);
9985                }
9986
9987                $eventCampaign->clear();
9988                $eventCampaign->set($eventPostData);
9989                if ($eventCampaign->save()) {
9990                    $res['error'] = false; 
9991                }
9992            }
9993        }
9994
9995        return json_encode($res);
9996    }
9997
9998    /**
9999     * @api {get} /teacher/api/getTeachinMaterialEngName getTeachinMaterialEngName()
10000     * @apiName getTeachinMaterialEngName
10001     * @apiGroup API
10002     * @apiDescription Get textbook english name
10003     * @apiSampleRequest off
10004     * 
10005     * @apiBody {String} connect_id The connect ID.
10006     * 
10007     * @apiSuccess {String} textbookName The textbook name.
10008     * 
10009     * @apiErrorExample {json} Success-Response:
10010     * "Category Subcategory : Textbook"
10011     */
10012    /**
10013     * get textbook english name 
10014     * connect_id - required
10015     * @return mixed textbookName string
10016     */
10017    public function getTeachinMaterialEngName() {
10018        $this->autoRender = false;
10019        $textbookName = '';
10020        if ( !empty($this->request->query['connect_id']) ) {
10021            $textbookNameRaw = $this->TextbookConnect->getTextbookName(array(
10022                'connect_id' => $this->request->query['connect_id'],
10023                'native_language' => 'en'
10024            ));            
10025            $textbookName .= !empty($textbookNameRaw['textbook_category_name']) ? $textbookNameRaw['textbook_category_name'] : '';
10026            $textbookName .= !empty($textbookNameRaw['textbook_subcategory_name']) ? ' ' . $textbookNameRaw['textbook_subcategory_name'] : '';
10027            $textbookName .= !empty($textbookNameRaw['textbook_name']) ? ' : ' . $textbookNameRaw['textbook_name'] : '';
10028        }
10029        return $textbookName;        
10030    }
10031
10032    /**
10033     * @api {get} /teacher/api/teacherAudioDataMemCached teacherAudioDataMemCached()
10034     * @apiName teacherAudioDataMemCached
10035     * @apiGroup API
10036     * @apiDescription Teacher audio data memcached
10037     * @apiSampleRequest off
10038     * 
10039     * @apiBody {String} chat_hash The chat hash.
10040     * 
10041     * @apiSuccess {Boolean} success The status of the request.
10042     * @apiSuccess {String} type The type of the request.
10043     * @apiSuccess {Array} value The value of the request.
10044     * @apiSuccess {String} value.teacher_audio_data_percentage The teacher audio data percentage.
10045     * @apiSuccess {String} value.teacher_audio_interval The teacher audio interval.
10046     * 
10047     * @apiErrorExample {json} Success-Response:
10048     *     {
10049     *       "success": true,
10050     *       "type": "update",
10051     *       "value": {
10052     *           "teacher_audio_data_percentage": 50,
10053     *           "teacher_audio_interval": 10
10054     *       }
10055     *     }
10056     *
10057     * @apiErrorExample {js} Used in: Elements
10058     * Location: "view/Elements/chat_area_js.php"
10059     */
10060    /**
10061    *  // Check if teacher has a recent teacher audio data memcached if none ->add if has ->update
10062    * // get memcache data by key
10063    * @return bool
10064    */
10065    public function teacherAudioDataMemCached () {
10066        $this->layout = $this->autoRender = false;
10067        $chatHash = (isset($this->request->data['chat_hash'])) ? $this->request->data['chat_hash'] : null;
10068        $teacherAudioData = (isset($this->request->data['teacher_audio_data'])) ? $this->request->data['teacher_audio_data'] : 0;
10069        $teacherAudioInterval = (isset($this->request->data['teacher_audio_interval'])) ? $this->request->data['teacher_audio_interval'] : 0;
10070        $isDelete = (isset($this->request->data['is_delete'])) ? $this->request->data['is_delete'] : false;
10071        $res = array('success' => false);
10072
10073        //include if not exist
10074        if (!class_exists('myMemcached')) {
10075            App::uses('myMemcached', 'Lib');
10076        }
10077
10078        $memcached = new myMemcached();
10079        $memKey = 'teacher_audio_data_acquisition_' . $chatHash;
10080        $memKey2 = 'teacher_audio_interval_' . $chatHash;
10081        $memTeacherAudioData = $memcached->get($memKey);
10082        $memTeacherInterval = $memcached->get($memKey2);
10083
10084        //creating or updating memcache
10085        if ($chatHash && $teacherAudioData) {
10086            $this->log("kevin trigger ", "debug");
10087            if ($memTeacherAudioData) {
10088                $memcached->set(array(
10089                    'key' => $memKey,
10090                    'value' => $teacherAudioData,
10091                ));
10092                $memcached->set(array(
10093                    'key' => $memKey2,
10094                    'value' => $teacherAudioInterval,
10095                ));
10096                $res = array(
10097                    'success' => true,
10098                    'type' => 'update',
10099                    'value' =>  array(
10100                        'teacher_audio_data_percentage' => $teacherAudioData,
10101                        'teacher_audio_interval' => $teacherAudioInterval,
10102                    )
10103                );
10104            } else {
10105                $memcached->set(array(
10106                    'key' => $memKey,
10107                    'value' => $teacherAudioData,
10108                    'expire' => 1800 // 30 mins
10109                ));
10110                $memcached->set(array(
10111                    'key' => $memKey2,
10112                    'value' => $teacherAudioInterval,
10113                    'expire' => 1800 // 30 mins
10114                ));
10115                $res = array(
10116                    'success' => true,
10117                    'type' => 'create',
10118                    'value' =>  array(
10119                        'teacher_audio_data_percentage' => $teacherAudioData,
10120                        'teacher_audio_interval' => $teacherAudioInterval,
10121                    )
10122                );
10123            }
10124        } else if ($chatHash && $isDelete) { //for deletion of memcache data
10125            $memcached->delete($memKey);
10126            $memcached->delete($memKey2);
10127            $res = array('success' => true);
10128        }
10129        
10130        return json_encode($res);    
10131    }
10132
10133    /**
10134     * @api {get} /teacher/api/nextLessonReservation nextLessonReservation()
10135     * @apiName nextLessonReservation
10136     * @apiGroup API
10137     * @apiDescription Get next lesson reservation
10138     * @apiSampleRequest off
10139     * 
10140     * @apiSuccess {Boolean} nowStatus The status of the request.
10141     * @apiSuccess {Boolean} nowStatusRemarks The status remarks of the request.
10142     * @apiSuccess {Array} next_lesson_reservations The next lesson reservations.
10143     * @apiSuccess {String} next_lesson_reservations.id The ID.
10144     * @apiSuccess {String} next_lesson_reservations.lesson_time The lesson time.
10145     * @apiSuccess {String} next_lesson_reservations.profile The profile.
10146     * @apiSuccess {String} next_lesson_reservations.nickname The nickname.
10147     * @apiSuccess {String} next_lesson_reservations.start_time The start time.
10148     * @apiSuccess {String} next_lesson_reservations.end_time The end time.
10149     * 
10150     * @apiErrorExample {json} Success-Response:
10151     *     {
10152     *       "nowStatus": 5,
10153     *       "nowStatusRemarks": 1,
10154     *       "next_lesson_reservations": [
10155     *           {
10156     *               "id": 1,
10157     *               "lesson_time": "2023-10-01 10:00:00",
10158     *               "profile": "url",
10159     *               "nickname": "John",
10160     *               "start_time": "Oct 01 10:00AM",
10161     *               "end_time": "10:30AM"
10162     *           }
10163     *       ]
10164     *     }
10165     *
10166     * @apiErrorExample {json} Error-Response:
10167     *     {
10168     *       "nowStatus": false,
10169     *       "nowStatusRemarks": false,
10170     *       "next_lesson_reservations": []
10171     *     }
10172     * 
10173      * @apiErrorExample {js} Used in: AngularJS
10174      * Location: "webroot/js/ng/controller/home.js"
10175      * Location: "webroot/js/ng/app.js"
10176      * Location: "webroot/js/ng/home.js"
10177     */
10178    public function nextLessonReservation(){
10179        $this->autoRender = false;
10180        $view = new View($this, false);
10181        
10182        $teacher_id = $this->Auth->user('id');
10183
10184        if(empty($teacher_id) || !$teacher_id){
10185            return false;
10186        }
10187        $startDate = date('Y-m-d H:i:s');
10188        $schedules = array(); // set default as empty
10189        $result = array(); // set default as empty
10190
10191        $nowStatus = !empty($this->Session->read('Teacher.status')) ? $this->Session->read('Teacher.status') : false;
10192        $nowStatusRemarks = !empty($this->Session->read('Teacher.remarks')) ? $this->Session->read('Teacher.remarks') : false;
10193
10194        $lsParams = array(
10195            'args' => array(
10196                'fields' => array(
10197                    'LessonSchedule.lesson_time',
10198                    'User.nickname',
10199                    'User.id',
10200                    'User.image_url',
10201                    'User.profile_image',
10202                    'ShiftWorkon.do_live_flg',
10203                    'ShiftWorkon.status',
10204                    'ShiftWorkon.live_lesson_flg'
10205                ),
10206                'joins' => array(
10207                    array(
10208                        'table' => 'shift_workons',
10209                        'alias' => 'ShiftWorkon',
10210                        'type' => 'LEFT',
10211                        'conditions' => array(
10212                            'ShiftWorkon.teacher_id = LessonSchedule.teacher_id',
10213                            'ShiftWorkon.lesson_time = LessonSchedule.lesson_time'
10214                        )
10215                    )
10216                ),
10217                'conditions' => array(
10218                    'LessonSchedule.teacher_id' => $teacher_id,
10219                    'LessonSchedule.status' => 1,
10220                    'LessonSchedule.lesson_time >=' => $startDate,
10221                    'User.status' => 1
10222                ),
10223                'order' => 'LessonSchedule.lesson_time ASC',
10224                'limit' => 2
10225            )
10226        );
10227
10228        // NJ-64088
10229        $schedules = $this->LessonSchedule->getSchedules($lsParams);
10230        
10231        if (is_array($schedules) && $schedules) {
10232            foreach($schedules as $key=>$schedule){
10233                $user = new UserTable($schedule['User']);
10234                $nickname = $schedule['User']['nickname'];
10235                $nickname = strlen($nickname) > 20 ? trim(substr($nickname,0,20))."..." : $nickname;
10236                $start_time = date('Md g:i A',strtotime($this->timeDiff.' minutes'.$schedule['LessonSchedule']['lesson_time']));
10237                $end_time = date('g:i A',strtotime($this->timeDiff.' minutes'.$schedule['LessonSchedule']['lesson_time'].' +30 minutes'));
10238
10239                // Check if the dates are the same
10240                $is_live = $schedule['ShiftWorkon']['live_lesson_flg'] == 1
10241                && $schedule['ShiftWorkon']['do_live_flg'] == 1
10242                && $schedule['ShiftWorkon']['status'] == 1
10243                ? 1 : 0;
10244
10245                $result[$key]['id'] = $schedule['User']['id'];
10246                $result[$key]['lesson_time'] = $schedule['LessonSchedule']['lesson_time'];
10247                $result[$key]['profile'] = $user->getImageUrl();
10248                $result[$key]['nickname'] = $nickname;
10249                $result[$key]['start_time'] = $start_time;
10250                $result[$key]['end_time'] = $end_time;
10251                $result[$key]['is_live'] = $is_live;
10252            }
10253        }
10254
10255        return json_encode(array('nowStatus'=>$nowStatus,'nowStatusRemarks'=>$nowStatusRemarks,'next_lesson_reservations'=>$result));
10256    }
10257
10258    /**
10259     * @api {post} /teacher/api/otherAudioDataMemCached otherAudioDataMemCached()
10260     * @apiName otherAudioDataMemCached
10261     * @apiGroup API
10262     * @apiDescription Other audio data memcached
10263     * @apiSampleRequest off
10264     * 
10265     * @apiBody {String} chat_hash The chat hash.
10266     * @apiBody {String} teacher_audio_data_percentage The teacher audio data percentage.
10267     * @apiBody {String} student_audio_data_percentage The student audio data percentage.
10268     * 
10269     * @apiSuccess {Boolean} success The status of the request.
10270     * @apiSuccess {String} type The type of the request.
10271     * @apiSuccess {Array} values The values of the request.
10272     * @apiSuccess {String} values.teacher_audio_data_percentage The teacher audio data percentage.
10273     * @apiSuccess {String} values.student_audio_data_percentage The student audio data percentage.
10274     * 
10275     * @apiErrorExample {json} Success-Response:
10276     *     {
10277     *       "success": true,
10278     *       "type": "update",
10279     *       "values": {
10280     *           "teacher_audio_data_percentage": 50,
10281     *           "student_audio_data_percentage": 50
10282     *       }
10283     *     }
10284     * 
10285     * @apiErrorExample {js} Used in: Elements
10286     * Location: "view/Elements/chat_area_js.php"
10287     */
10288    /**
10289    *  // Check if teacher has a recent teacher audio data memcached if none ->add if has ->update
10290    * // get memcache data by key
10291    * @return bool
10292    */
10293    public function otherAudioDataMemCached () {
10294        $this->layout = $this->autoRender = false;
10295        $chatHash = (isset($this->request->data['chat_hash'])) ? $this->request->data['chat_hash'] : null;
10296        $studentAudioDataPercentage = (isset($this->request->data['student_audio_data_percentage'])) ? $this->request->data['student_audio_data_percentage'] : 0;
10297        $teacherAudioDataPercentage = (isset($this->request->data['teacher_audio_data_percentage'])) ? $this->request->data['teacher_audio_data_percentage'] : 0;
10298        $isDelete = (isset($this->request->data['is_delete'])) ? $this->request->data['is_delete'] : false;
10299        $res = array('success' => false);
10300
10301        //include if not exist
10302        if (!class_exists('myMemcached')) {
10303            App::uses('myMemcached', 'Lib');
10304        }
10305
10306        $memcached = new myMemcached();
10307
10308        $memKey = 'teacher_audio_data_percentage_' .$chatHash;
10309        $memKey2 = 'student_audio_data_percentage_' .$chatHash;
10310        $memTeacherAudioDataPercentage = $memcached->get($memKey);
10311        $memStudentAudioDataPercentage = $memcached->get($memKey2);
10312
10313        if($chatHash && $teacherAudioDataPercentage != null && $studentAudioDataPercentage != null) {
10314            if ($memTeacherAudioDataPercentage && $memStudentAudioDataPercentage) {
10315                $memcached->set(array(
10316                    'key' => $memKey,
10317                    'value' => $teacherAudioDataPercentage,
10318                ));
10319                $memcached->set(array(
10320                    'key' => $memKey2,
10321                    'value' => $studentAudioDataPercentage,
10322                ));
10323                $res = array(
10324                    'success' => true,
10325                    'type' => 'update',
10326                    'values' => array(
10327                        'teacher_audio_data_percentage' => $teacherAudioDataPercentage,
10328                        'student_audio_data_percentage' => $studentAudioDataPercentage
10329                    )
10330                );
10331            } else {
10332                $memcached->set(array(
10333                    'key' => $memKey,
10334                    'value' => $teacherAudioDataPercentage,
10335                    'expire' => 1800 // 30 mins
10336                ));
10337                $memcached->set(array(
10338                    'key' => $memKey2,
10339                    'value' => $studentAudioDataPercentage,
10340                    'expire' => 1800 // 30 mins
10341                ));
10342                $res = array(
10343                    'success' => true,
10344                    'type' => 'create',
10345                    'values' => array(
10346                        'teacher_audio_data_percentage' => $teacherAudioDataPercentage,
10347                        'student_audio_data_percentage' => $studentAudioDataPercentage
10348                    )
10349                );
10350            }
10351        } else if ($chatHash && $isDelete) { //for deletion of memcache data
10352            $memcached->delete($memKey);
10353            $memcached->delete($memKey2);
10354            $res = array('success' => true);
10355        }
10356        
10357        return json_encode($res);
10358    }
10359
10360    /**
10361     * @api {get} /teacher/api/fcmWebNotification fcmWebNotification()
10362     * @apiName fcmWebNotification
10363     * @apiGroup API
10364     * @apiDescription Send web push notification to idle teachers
10365     * @apiSampleRequest off
10366     * 
10367     * @apiBody {String} device_token The device token.
10368     * @apiBody {String} target_url The target URL.
10369     * 
10370     * @apiSuccess {Boolean} success The status of the request.
10371     * @apiSuccess {Array} fcm_response The FCM response. (`$fcmApp->curlFCM($payload, $appServerKey)`)
10372     * 
10373     * @apiErrorExample {json} Success-Response:
10374     *     {
10375     *       "success": true,
10376     *       "fcm_response": {...}
10377     *     }
10378     *
10379      * @apiErrorExample {js} Used in: JS
10380      * Location: "webroot/js/custom_sound-recorder.js"
10381      * Location: "webroot/js/teacher-fcm-notif-config.js"
10382     */
10383    /*
10384     * Send web push notification to idle teachers
10385     * @param device_token - required
10386     * @return res json
10387     */ 
10388    public function fcmWebNotification(){
10389        $this->autoRender = false;
10390        $res = array('success' => false);
10391        if (
10392            $this->request->is('ajax')
10393            && !empty($this->request->data['device_token'])
10394            && !empty($this->request->data['target_url'])
10395        ) {
10396            $fcmApp = new firebaseCloudMsg();
10397            $appServerKey = Configure::read('fdcm_web.server_key');
10398            $payload = array(
10399                'to' => $this->request->data['device_token'],
10400                'data' => array(
10401                    'title' => 'test',
10402                    'body' => 'test'
10403                ),
10404                'notification' => array(
10405                    'title' => 'Native Camp',
10406                    'body' => 'Your status has been changed to NOT STANDBY',
10407                    'content_available' => true,
10408                    'click_action' => $this->request->data['target_url'],
10409                    'icon' => 'teacher/images/common/nc_logo.png'
10410                ),
10411                'webpush' => array(
10412                    'fcm_options' => array()
10413                )
10414            );
10415            $fcmPush = $fcmApp->curlFCM($payload, $appServerKey);
10416            $res['success'] = true;
10417            $res['fcm_response'] = $fcmPush;
10418        }
10419        return json_encode($res);
10420    }
10421
10422    /**
10423     * @api {post} /teacher/api/studentLeftLessonUpdate studentLeftLessonUpdate()
10424     * @apiName studentLeftLessonUpdate
10425     * @apiGroup API
10426     * @apiDescription Update student left lesson
10427     * @apiSampleRequest off
10428     * 
10429     * @apiBody {String} chat_hash The chat hash.
10430     * @apiBody {String} user_id The user ID.
10431     * 
10432     * @apiSuccess {Boolean} success The status of the request.
10433     * 
10434     * @apiErrorExample {json} Success-Response:
10435     *     {
10436     *       "success": true
10437     *     }
10438     *
10439     * @apiErrorExample {json} Error-Response:
10440     *     {
10441     *       "success": false
10442     *     }
10443     */
10444    /**
10445     * [NJ-18003] if student left the reseved lesson, current use/call from reserved live lesson
10446     */
10447    public function studentLeftLessonUpdate() {
10448        $this->autoRender = false;
10449        $res = ['success' => false];
10450
10451        if ($this->request->is('ajax') && !empty($this->request->data['chat_hash']) && !empty($this->request->data['user_id'])) {
10452            $onAir = $this->LessonOnair->find('first', [
10453                'fields' => [
10454                    'LessonOnair.id', 
10455                    'LessonOnair.lesson_type', 
10456                    'LessonOnair.leave_lesson',
10457                    'LessonOnair.user_id'
10458                ],
10459                'conditions' => [
10460                    'LessonOnair.chat_hash' => trim($this->request->data['chat_hash'])
10461                ],
10462                'recursive' => -1
10463            ]);
10464
10465            if(
10466                $onAir && 
10467                $onAir['LessonOnair']['lesson_type'] == Configure::read('lesson.type.reservation') &&
10468                $onAir['LessonOnair']['user_id'] == $this->request->data['user_id']
10469            ) {
10470                try {
10471                    $this->LessonOnair->read(['leave_lesson'], $onAir['LessonOnair']['id']);
10472                    $this->LessonOnair->saveField('leave_lesson', 1);
10473                    $res = ['success' => true];
10474                } catch (Exception $e) {
10475                    $this->log(__METHOD__. ' : [NJ-18003] leave_lesson update] -> ' . $e->getMessage(), 'debug');
10476                }
10477            }
10478        }
10479
10480        return json_encode($res);
10481    }
10482
10483    /**
10484     * @api {post} /teacher/api/updateCurrentTutorialModalSteps updateCurrentTutorialModalSteps()
10485     * @apiName updateCurrentTutorialModalSteps
10486     * @apiGroup API
10487     * @apiDescription Update current tutorial modal steps
10488     * @apiSampleRequest off
10489     * 
10490     * @apiBody {String} current The current step.
10491     * @apiBody {String} show The show step.
10492     * 
10493     * @apiSuccess {Boolean} status The status of the request.
10494     * @apiSuccess {String} msg The message.
10495     * @apiSuccess {Array} steps The steps. (`$memcached->get("modal_and_tutorial{$teacherID}")`)
10496     * 
10497     * @apiErrorExample {json} Success-Response:
10498     *     {
10499     *       "status": true,
10500     *       "msg": "Success message",
10501     *       "steps": {...}
10502     *     }
10503     *
10504     * @apiErrorExample {json} Error-Response:
10505     *     {
10506     *       "status": false
10507     *     }
10508     * 
10509      * @apiErrorExample {js} Used in: AngularJS
10510      * Location: "webroot/js/ng/services.js"
10511     */
10512    public function updateCurrentTutorialModalSteps(){
10513        $this->layout = $this->autoRender = false;
10514        $response = array('status' => false);
10515
10516        // To reset the modal
10517        if(isset($_GET['reset']) && $_GET['reset'] == 1){
10518            $teacherID = $this->Auth->user('id');
10519            $memcached = new myMemcached();
10520            echo '<pre>';
10521            print_r($memcached->get("modal_and_tutorial{$teacherID}"));
10522            print_r($memcached->delete("modal_and_tutorial{$teacherID}"));
10523            echo "Teacher : {$teacherID} MODAL & TUTORIAL RESET!!";
10524            return;
10525        }
10526        
10527        if ($this->request->is('post')) {
10528            $reqData = $this->request->data;
10529            $teacherID = $this->Auth->user('id');
10530
10531            if(!empty($teacherID) && isset($reqData['current']) && isset($reqData['show'])){
10532                $memcached = new myMemcached();
10533                $updateModalTutorial = $memcached->set(array(
10534                    'key' => "modal_and_tutorial{$teacherID}",
10535                    'value' => array(
10536                        'current' => $reqData['current'], # == 1 - Modal A | 2 - Tutorial 1 | 3 - Modal B | 4 - Tutorial 2 | 5 - Modal C | 6 - Tutorial 3 ===
10537                        'show' => $reqData['show']
10538                    ), 
10539                    'expire' => 864000  # 10 days (10 * 24 * 3600 seconds)
10540                ));
10541
10542                $response['status'] = (is_array($updateModalTutorial) && $updateModalTutorial['error']) ? false : true;
10543                $response['msg'] = (is_array($updateModalTutorial)) ?? $updateModalTutorial['content'];
10544                $response['steps'] = $memcached->get("modal_and_tutorial{$teacherID}");
10545            }else{
10546                $response['status'] = false;
10547            }
10548        }
10549
10550        return json_encode($response);
10551    }
10552    
10553    /**
10554     * @api {get} /teacher/api/getUnviewedWarning getUnviewedWarning()
10555     * @apiName getUnviewedWarning
10556     * @apiGroup API
10557     * @apiDescription Get unviewed warning
10558     * @apiSampleRequest off
10559     * 
10560     * @apiSuccess {String} warningCount The warning count.
10561     * 
10562     * @apiErrorExample {json} Success-Response:
10563     *     {
10564     *       "warningCount": 3
10565     *     }
10566     *
10567      * @apiErrorExample {js} Used in: AngularJS
10568      * Location: "webroot/js/ng/controller/home.js"
10569      * Location: "webroot/js/ng/app.js"
10570      */
10571    public function getUnviewedWarning($id = 0) {
10572        $this->autoRender = false;
10573        $warningCount = $this->Warning->countReadWarning($id);
10574        return $warningCount;
10575    }
10576
10577    /**
10578     * @api {get} /teacher/api/showReservedLessonSpecialModal showReservedLessonSpecialModal()
10579     * @apiName showReservedLessonSpecialModal
10580     * @apiGroup API
10581     * @apiDescription Check if sapuri school user and show reserved lesson special modal(1 time only)
10582     * @apiSampleRequest off
10583     * 
10584     * @apiBody {String} userId The user ID.
10585     * @apiBody {String} lessonTime The lesson time.
10586     * 
10587     * @apiSuccess {Boolean} showModal The status of the request.
10588     * 
10589     * @apiErrorExample {json} Success-Response:
10590     *     {
10591     *       "showModal": true
10592     *     }
10593     *
10594      * @apiErrorExample {js} Used in: AngularJS
10595      * Location: "webroot/js/ng/app.js"
10596     */
10597    /**
10598     * Check if sapuri school user and show reserved lesson special modal(1 time only)
10599     */
10600    public function showReservedLessonSpecialModal($userId = null, $lessonTime = '') {
10601        $this->autoRender = false;
10602
10603        if (!isset($userId) || empty($lessonTime)){
10604            return false;
10605        }
10606
10607        $teacherId = $this->Auth->user('id');
10608        $memKey = 'tos_reserved_lesson_special_modal' . $userId . '_' . $teacherId . '_' . $lessonTime;
10609
10610        if (!class_exists('myMemcached')) {
10611            App::uses('myMemcached', 'Lib');
10612        }
10613
10614        $showModal = false;
10615        $memcached = new myMemcached();
10616        $memData = $memcached->get($memKey);
10617
10618        // check if memcache exist
10619        if (!$memData) {
10620            $showModal = true;
10621            $memcached->set([
10622                'key' => $memKey,
10623                'value' => true,
10624                'expire' => 1200 // 20 mins
10625            ]);
10626        }
10627
10628        return json_encode(['showModal' => $showModal]);
10629    }
10630
10631    /**
10632     * @api {post} /teacher/api/getLearningKitCount getLearningKitCount()
10633     * @apiName getLearningKitCount
10634     * @apiGroup API
10635     * @apiDescription Get learning kit count
10636     * @apiSampleRequest off
10637     * 
10638     * @apiSuccess {String} new_textbook_cnt The new textbook count.
10639     * 
10640     * @apiErrorExample {json} Success-Response:
10641     *     {
10642     *       "new_textbook_cnt": 5
10643     *     }
10644     * 
10645      * @apiErrorExample {js} Used in: AngularJS
10646      * Location: "webroot/js/ng/controller/home.js"
10647      * Location: "webroot/js/ng/app.js"
10648     */
10649    public function getLearningKitCount(){
10650        $this->autoRender = false;
10651        $teacherId = $this->Auth->user('id');
10652        $type = 1;
10653        $new_textbook_cnt = 0;
10654
10655        if($this->request->is('ajax')){
10656            $new_textbook_cnt = $this->TeacherBadge->getTeacherNewBadge(array('teacher_id' => $teacherId, 'type' => $type));
10657        }
10658
10659        return $new_textbook_cnt;
10660    }
10661
10662    /**
10663     * @api {post} /teacher/api/dynamicModalData dynamicModalData()
10664     * @apiName dynamicModalData
10665     * @apiGroup API
10666     * @apiDescription Get dynamic modal data
10667     * @apiSampleRequest off
10668     * 
10669     * @apiBody {String} user_id The user ID.
10670     * 
10671     * @apiSuccess {Boolean} status The status of the request.
10672     * @apiSuccess {Array} data The data.
10673     * @apiSuccess {String} data.lesson_count The lesson count.
10674     * @apiSuccess {String} data.member_type The member type.
10675     * @apiSuccess {String} data.member_index The member index.
10676     * @apiSuccess {String} data.trial_count The trial count.
10677     * @apiSuccess {String} data.withdrawalCounselingFlag The withdrawal counseling flag.
10678     * @apiSuccess {Array} data.onair The onair data. (`$this->LessonOnair->find`)
10679     * 
10680     * @apiErrorExample {json} Success-Response:
10681     *     {
10682     *       "status": true,
10683     *       "data": {
10684     *           "lesson_count": 10,
10685     *           "member_type": "Premium",
10686     *           "member_index": 2,
10687     *           "trial_count": 7,
10688     *           "withdrawalCounselingFlag": 1,
10689     *           "onair": {...}
10690     *       }
10691     *     }
10692     *
10693      * @apiErrorExample {js} Used in: AngularJS
10694      * Location: "webroot/js/ng/app.js"
10695     */
10696    # NJ-24697
10697    public function dynamicModalData(){
10698        $this->autoRender = false; # preventing from loading 404 hmtl
10699        $response = array('status' => false);
10700        $withdrawalCounselingFlag = 0;
10701        # check if the request is pot
10702        if($this->request->is('post')){
10703
10704            $data = $this->request->data;
10705            $counselor = $this->Auth->user('counseling_flg');
10706            
10707            if($counselor){
10708
10709                // check if not empty user id 
10710                $userID = (!empty($data["user_id"])) ? $data["user_id"] : null;
10711
10712                $this->LessonOnair->clear();
10713                $this->LessonOnair->openDBReplica();
10714                $onair = $this->LessonOnair->find('first',array(
10715                    'fields' => array(
10716                        "LessonOnair.user_id",
10717                        'LessonOnair.withdrawal_counseling_flg'
10718                    ),
10719                    'conditions' => array(
10720                        "LessonOnair.teacher_id" => $this->Auth->user('id')
10721                    ),
10722                    'recursive' => -1
10723                ));
10724                $this->LessonOnair->closeDBReplica();
10725
10726                $withdrawalCounselingFlag = (isset($onair['LessonOnair']['withdrawal_counseling_flg'])) ? $onair['LessonOnair']['withdrawal_counseling_flg'] : 0;
10727                $userID = !$userID ? (isset($onair['LessonOnair']['user_id']) ? $onair['LessonOnair']['user_id'] : $userID) : $userID;
10728
10729                $userData = $this->User->find('first', array(
10730                    'fields' => array(
10731                        'status',
10732                        'corporate_type',
10733                        'corporate_individual_payment_flg',
10734                        'fail_flg',
10735                        'double_check_flg',
10736                        'charge_flg',
10737                        'parent_id',
10738                        'payment_plan_id',
10739                        'id',
10740                        'hash16',
10741                        'studysapuri_id',
10742                        'corporate_id',
10743                        'nickname',
10744                        'created'
10745                    ),
10746                    'conditions' => array('id' => $userID),
10747                    'recursive' => -1
10748                ));
10749                
10750                $userTable = new UserTable($userData['User']);
10751
10752                $membershipTypeIndex = $userTable->getMembershipTypeIndex();
10753                $stdLesson = $this->LessonOnairsLog->countLessons($userID);
10754
10755                # calculate the remaining days of trial
10756                $startDate = new DateTime($userData["User"]["created"]);
10757                $currentDate = new DateTime();
10758                $interval = $startDate->diff($currentDate);
10759                $remainingDays = $interval->days > 7 ? 7 : $interval->days;
10760
10761                $response["status"] = true;
10762                $response["data"] = array(
10763                    "lesson_count" => $stdLesson,
10764                    "member_type" => UserTable::getJpnMembershipTypeData()[$membershipTypeIndex],
10765                    "member_index" => $membershipTypeIndex,
10766                    "trial_count" =>  $remainingDays,
10767                    "withdrawalCounselingFlag" => $withdrawalCounselingFlag,
10768                    'onair' => $onair
10769                );
10770            }
10771        }else{
10772            $response["status"] = false;
10773        }
10774 
10775        return json_encode($response); 
10776    }
10777
10778    /**
10779     * @api {post} /teacher/api/saveBookmark saveBookmark()
10780     * @apiName saveBookmark
10781     * @apiGroup API
10782     * @apiDescription Save bookmark data
10783     * @apiSampleRequest off
10784     * 
10785     * @apiBody {String} lesson_identifier The lesson identifier.
10786     * @apiBody {String} textbook_connect_id The textbook connect ID.
10787     * @apiBody {String} callan_bookmark_id The callan bookmark ID.
10788     * @apiBody {String} type The type.
10789     * 
10790     * @apiSuccess {Boolean} status The status of the request.
10791     * @apiSuccess {Array} data The data. (`$this->CallanBookmarkOption->find`)
10792     * 
10793     * @apiError {Object} error The error object.
10794     * @apiError {String} error.message The error message. 
10795     * 
10796     * @apiErrorExample {json} Success-Response:
10797     *     {
10798     *       "status": true,
10799     *       "data": {...}
10800     *     }
10801     *
10802     * @apiErrorExample {json} Error-Response:
10803     *     {
10804     *       "status": false,
10805     *       "error": {
10806     *           "message": "Invalid bookmark ID"
10807     *       }
10808     *     }
10809     * @apiErrorExample {js} Used in: JS
10810     * Location: "view/HtmlTextBook/index.php"
10811     */
10812    /*
10813    * NJ-33115: Save bookmark data
10814    */
10815    public function saveBookmark(){
10816        $this->autoRender = false;
10817        $response = array('status' => false);
10818
10819        if($this->request->is('post')){
10820            $chatHash = (isset($this->request->data['lesson_identifier'])) ? $this->request->data['lesson_identifier'] : null;
10821            $textbook_connect_id = (isset($this->request->data['textbook_connect_id'])) ? $this->request->data['textbook_connect_id'] : null;
10822            $callan_bookmark_string = (isset($this->request->data['callan_bookmark_id'])) ? $this->request->data['callan_bookmark_id'] : null;
10823            $type = (isset($this->request->data['type'])) ? $this->request->data['type'] : null;
10824            $category_id = isset($this->request->data['category_id']) ? $this->request->data['category_id'] : null;
10825
10826            // $bookmarkParts = explode('_', $callan_bookmark_string);
10827            // $callan_bookmark_id = end($bookmarkParts);
10828            $callan_bookmark_id = preg_replace('/[^0-9]/', '', $callan_bookmark_string);
10829
10830            // validate bookmark ID
10831            $this->CallanBookmarkOption->openDBReplica();
10832            $checkBookmarkData = $this->CallanBookmarkOption->find('first', array(
10833                'fields' => array(
10834                    'id',
10835                    'stage',
10836                    'page',
10837                    'type',
10838                    'headword',
10839                    'paragraph'
10840                ),
10841                'conditions' => array('id' => $callan_bookmark_id),
10842                'recursive' => -1
10843            ));
10844            $this->CallanBookmarkOption->closeDBReplica();
10845
10846            if (!$checkBookmarkData) {
10847                $response['error']['message'] = "Invalid bookmark ID";
10848                return json_encode($response); 
10849            }
10850            
10851            if(isset($checkBookmarkData['CallanBookmarkOption']['stage'])){
10852                $this->CallanBookmarkOption->openDBReplica();
10853                $getTypeFormat = $this->CallanBookmarkOption->find('first', array(
10854                    'fields' => array(
10855                        "page", "type", "GROUP_CONCAT(paragraph ORDER BY paragraph SEPARATOR '|')  as paragraph"
10856                    ),
10857                    'conditions' => array(
10858                        'stage' => $checkBookmarkData['CallanBookmarkOption']['stage'],
10859                        'page' => $checkBookmarkData['CallanBookmarkOption']['page']
10860                    ),
10861                    'group' => 'page',
10862                    'recursive' => -1
10863                ));
10864                $this->CallanBookmarkOption->closeDBReplica();
10865            }
10866            
10867            // NJ-33801 - Change Type Format after bookmark
10868            $paragraphsInfo = !empty($getTypeFormat) ? explode('|', $getTypeFormat[0]['paragraph']) : null;
10869            
10870            if(!empty($paragraphsInfo)){
10871                $paragraphsInfo = explode('|', $getTypeFormat[0]['paragraph']);                
10872                $first = $paragraphsInfo[0];
10873                $last = end($paragraphsInfo);
10874                
10875                preg_match('/Lesson\s*(\d+)\s*,\s*(?:pp\s*(\d+)|(\d+))/i', $first, $firstMatch);
10876                preg_match('/Lesson\s*(\d+)\s*,\s*(?:pp\s*(\d+)|(\d+))/i', $last, $lastMatch);
10877                
10878                $lessonStart = $firstMatch[1];
10879                $lessonEnd = $lastMatch[1];
10880                $pageStart = $firstMatch[2];
10881                $pageEnd = $lastMatch[2];
10882                
10883                $lessonRange = ($lessonStart === $lessonEnd) ? "Lesson $lessonStart" : "Lesson $lessonStart~$lessonEnd";
10884                
10885                $pageRange = ($pageStart === $pageEnd) ? "pp$pageStart" : "pp$pageStart~$pageEnd";
10886            
10887                $formattedString = "".$checkBookmarkData['CallanBookmarkOption']['type']." ($lessonRange$pageRange)";
10888                
10889                $checkBookmarkData['CallanBookmarkOption']['formattedtype'] = $formattedString;    
10890            }else{
10891                $response['error']['message'] = "Paragraph Information is empty";
10892                return json_encode($response);
10893            }
10894            
10895            // get student and teacher IDs
10896            $this->LessonOnair->openDBReplica();
10897            $onairData = $this->LessonOnair->find('first', array(
10898                'fields' => array(
10899                    'LessonOnair.user_id',
10900                    'LessonOnair.teacher_id',
10901                    'LessonOnair.connect_id'
10902                ),
10903                'conditions' => array('chat_hash' => $chatHash),
10904                'recursive' => -1
10905            ));
10906            $this->LessonOnair->closeDBReplica();
10907
10908            // validate callan lesson
10909            // TODO: add logic for outside lesson
10910            if(empty($onairData)){
10911
10912                $this->LessonOnairsLog->openDBReplica();
10913                $onairLogData = $this->LessonOnairsLog->find('first', array(
10914                    'fields' => array(
10915                        'LessonOnairsLog.user_id',
10916                        'LessonOnairsLog.teacher_id',
10917                        'LessonOnairsLog.connect_id',
10918                        'LessonOnairsLog.end_time'
10919                    ),
10920                    'conditions' => array('chat_hash' => $chatHash),
10921                    'recursive' => -1
10922                ));
10923                $this->LessonOnairsLog->closeDBReplica();
10924
10925                if(empty($onairLogData)){
10926                    $response['error']['message'] = "Cannot locate Callan Lesson Data";
10927                    $this->log("[saveBookmark] Cannot locate LessonOnairsLog: " . $chatHash, "error");
10928                    return json_encode($response); 
10929                }
10930
10931                // if greater than 3 minutes after lesson end
10932                $endTimeTimestamp = strtotime($onairLogData['LessonOnairsLog']['end_time']);
10933                $check3MinAfteLesson = (strtotime('now') <= $endTimeTimestamp + (3 * 60)) ? true : false; 
10934                if (!$check3MinAfteLesson) {
10935                    $response['error']['message'] = "Cannot bookmark after 3 minutes of lesson end";
10936                    $this->log("[saveBookmark] 3 minutes of lesson end: " . json_encode($onairLogData), "error");
10937                    return json_encode($response); 
10938                }
10939
10940                $user_id = $onairLogData['LessonOnairsLog']['user_id'];
10941                $teacher_id = $onairLogData['LessonOnairsLog']['teacher_id'];
10942                if (!isset($textbook_connect_id) || !$textbook_connect_id){
10943                    $textbook_connect_id = $onairLogData['LessonOnairsLog']['connect_id'];
10944                }
10945                
10946            } else {
10947                $user_id = $onairData['LessonOnair']['user_id'];
10948                $teacher_id = $onairData['LessonOnair']['teacher_id'];
10949                if (!isset($textbook_connect_id) || !$textbook_connect_id){
10950                    $textbook_connect_id = $onairData['LessonOnair']['connect_id'];
10951                }
10952            }
10953            
10954            $this->TextbookConnect->openDBReplica();
10955            $textbookConnectIds = $this->TextbookConnect->find('list', array(
10956                'fields' => array('id'),
10957                'conditions' => array('category_id' => $category_id),
10958                'recursive' => -1
10959            ));
10960            $this->TextbookConnect->closeDBReplica();
10961
10962            // get the current lesson boookmark
10963            $this->LessonBookmark->openDBReplica();
10964            $currentBookmark = $this->LessonBookmark->find('first', array(
10965                'fields' => array(
10966                    'LessonBookmark.id',
10967                    'LessonBookmark.callan_bookmark_id'
10968                ),
10969                'conditions' => array(
10970                    'LessonBookmark.user_id' => $user_id,
10971                    'LessonBookmark.textbook_connect_id IN ' => $textbookConnectIds,
10972                ),
10973                'recursive' => -1
10974            ));
10975            $this->LessonBookmark->closeDBReplica();
10976
10977            // delete 
10978            if (isset($currentBookmark['LessonBookmark']['callan_bookmark_id']) && $currentBookmark['LessonBookmark']['callan_bookmark_id'] == $callan_bookmark_id) {
10979                $response['deleted'] = false;
10980                $response['status'] = false;
10981                return json_encode($response); 
10982            }
10983            
10984            $bookmarkData = array(
10985                'chat_hash' => $chatHash,
10986                'textbook_connect_id' => $textbook_connect_id,
10987                'callan_bookmark_id' => $callan_bookmark_id,
10988                'user_id' => $user_id,
10989                'teacher_id' => $teacher_id,
10990                'type' => $type,
10991                'added_member_type' => 1
10992            );
10993            $this->log("[saveBookmark] latest bookmark: " . json_encode($bookmarkData), "error");
10994
10995            $this->LessonBookmark->clear();
10996            if ($currentBookmark) {
10997                $this->LessonBookmark->read(array_keys($bookmarkData), $currentBookmark['LessonBookmark']['id']);
10998            } else {
10999                // create new record
11000                $this->LessonBookmark->create();
11001            }
11002            $this->LessonBookmark->set($bookmarkData);
11003            if ($this->LessonBookmark->save()){
11004                $response['status'] = true;
11005
11006                // ~ if save status = true, return the bookmark data if exist
11007                if ($response['status'] && isset($checkBookmarkData['CallanBookmarkOption'])) {
11008                    $checkBookmarkData['CallanBookmarkOption']['category_id'] = $category_id;
11009                    $response['data'] = $checkBookmarkData['CallanBookmarkOption'];
11010                }
11011                
11012                // save to lesson bookmark history as well
11013                $this->LessonBookmarkHistory->openDBReplica();
11014                $currentBookmarkHistory = $this->LessonBookmarkHistory->find('first', array(
11015                    'fields' => array(
11016                        'LessonBookmarkHistory.id',
11017                        'LessonBookmarkHistory.callan_bookmark_id'
11018                    ),
11019                    'conditions' => array('user_id' => $user_id, 'chat_hash' => $chatHash),
11020                    'recursive' => -1
11021                ));
11022                $this->LessonBookmarkHistory->closeDBReplica();
11023                
11024                $this->LessonBookmarkHistory->clear();
11025                if ($currentBookmarkHistory) {
11026                    $this->LessonBookmarkHistory->read(array_keys($bookmarkData), $currentBookmarkHistory['LessonBookmarkHistory']['id']);
11027                }
11028                $this->LessonBookmarkHistory->set($bookmarkData);
11029                if (!$this->LessonBookmarkHistory->save()){
11030                    $this->log('[NJ-33115] failed to bookmark history' . json_encode($bookmarkData) , 'error');
11031                }
11032
11033                // update future reservation
11034                $this->LessonSchedulesExtend->updateUpcomingBookmarkedReservation($textbook_connect_id, $user_id);
11035            }
11036        }
11037    
11038        return json_encode($response); 
11039    }
11040
11041    /**
11042     * @api {post} /teacher/api/saveCallanStage saveCallanStage()
11043     * @apiName saveCallanStage
11044     * @apiGroup API
11045     * @apiDescription Save callan stage
11046     * @apiSampleRequest off
11047     * 
11048     * @apiBody {String} chat_hash The chat hash.
11049     * @apiBody {String} status The status.
11050     * @apiBody {String} user_id The user ID.
11051     * @apiBody {String} stageID The stage ID.
11052     * @apiBody {String} textbook_connect_id The textbook connect ID.
11053     * @apiBody {String} type The type.
11054     * 
11055     * @apiSuccess {Boolean} status The status of the request.
11056     * 
11057     * @apiErrorExample {json} Success-Response:
11058     *     {
11059     *       "status": true
11060     *     }
11061     *
11062     * @apiErrorExample {json} Error-Response:
11063     *     {
11064     *       "status": false
11065     *     }
11066     * 
11067     * @apiErrorExample {js} Used in: JS
11068     * Location: "view/HtmlTextBook/index.php"
11069     */
11070    public function saveCallanStage(){
11071        $this->autoRender = false;
11072        $response = array('status' => false);
11073
11074        if($this->request->is('post')){
11075            $chatHash = (isset($this->request->data['chat_hash'])) ? $this->request->data['chat_hash'] : null;
11076            $status = (isset($this->request->data['status'])) ? (int)$this->request->data['status'] : 0;
11077            $user_id = (isset($this->request->data['user_id'])) ? $this->request->data['user_id'] : 0;
11078            $stageID_str = (isset($this->request->data['stageID'])) ? $this->request->data['stageID'] : null;
11079            $textbook_connect_id = (isset($this->request->data['textbook_connect_id'])) ? $this->request->data['textbook_connect_id'] : null;
11080            $type = (isset($this->request->data['type'])) ? $this->request->data['type'] : null;
11081
11082
11083            $stageParts = explode('_', $stageID_str);
11084            $stageID = end($stageParts);
11085
11086            $this->UserNewLessonCallanProgress->openDBReplica();
11087            $stageProgress = $this->UserNewLessonCallanProgress->find('first', array(
11088                'fields' => array(
11089                    'id',
11090                    'status'
11091                ),
11092                'conditions' => array(
11093                    'user_id' => $user_id,
11094                    'callan_stage' => $stageID
11095                ),
11096                'recursive' => -1
11097            ));
11098            $this->UserNewLessonCallanProgress->closeDBReplica();
11099
11100            $progressData = array(
11101                "user_id" => $user_id,
11102                "callan_stage" => $stageID,
11103                "status" => $status
11104            );
11105
11106            if ($status == 1) {
11107                $progressData['completed_date'] = date('Y-m-d H:i:s'); // now
11108                $progressData['completion_chat_hash'] = $chatHash;
11109            } else {
11110                $progressData['completed_date'] = NULL;
11111                $progressData['completion_chat_hash'] = NULL;
11112            }
11113
11114            $this->log("Save Callan Stage: " . json_encode($progressData), "lesson_bookmark");
11115            $this->UserNewLessonCallanProgress->clear();
11116
11117            if ($stageProgress) {
11118                $this->UserNewLessonCallanProgress->read(array_keys($progressData), $stageProgress['UserNewLessonCallanProgress']['id']);
11119            }
11120
11121            $this->UserNewLessonCallanProgress->set($progressData);
11122                if ($this->UserNewLessonCallanProgress->save()){
11123                    $response['status'] = true;
11124                }
11125
11126            if ($status == 1) {
11127                // update lesson bookmark
11128                $this->CallanBookmarkOption->openDBReplica();
11129                $bookmark = $this->CallanBookmarkOption->find('first', array(
11130                    'fields' => array(
11131                        'id'
11132                    ),
11133                    'conditions' => array('stage' => $stageID+1),
11134                    'order' => 'id ASC',
11135                    'recursive' => -1
11136                ));
11137                $this->CallanBookmarkOption->closeDBReplica();
11138
11139                if ($bookmark) {
11140                    $this->LessonBookmark->openDBReplica();
11141                    $currentBookmark = $this->LessonBookmark->find('first', array(
11142                        'fields' => array(
11143                            'id',
11144                            'callan_bookmark_id'
11145                        ),
11146                        'conditions' => array('user_id' => $user_id),
11147                        'recursive' => -1
11148                    ));
11149                    $this->LessonBookmark->closeDBReplica();
11150
11151                    $bookmarkData = array(
11152                        'chat_hash' => $chatHash,
11153                        'textbook_connect_id' => $textbook_connect_id,
11154                        'callan_bookmark_id' => $bookmark['CallanBookmarkOption']['id'],
11155                        'user_id' => $user_id,
11156                        'teacher_id' => $this->Auth->user('id'),
11157                        'type' => $type
11158                    );
11159                    $this->log("Save Bookmark after Stage: " . json_encode($bookmarkData), "lesson_bookmark");
11160        
11161                    $this->LessonBookmark->clear();
11162                    if ($currentBookmark) {
11163                        $this->LessonBookmark->read(array_keys($bookmarkData), $currentBookmark['LessonBookmark']['id']);
11164                    }
11165                    $this->LessonBookmark->set($bookmarkData);
11166                    if ($this->LessonBookmark->save()){
11167                        $response['status'] = true;
11168                    }
11169
11170                    // update future reservation
11171                    $this->LessonSchedulesExtend->updateUpcomingBookmarkedReservation($textbook_connect_id, $user_id);
11172    
11173                }
11174    
11175            }
11176
11177        }
11178    
11179        return json_encode($response); 
11180    }
11181
11182    /**
11183     * @api {post} /teacher/api/fetchBookmark fetchBookmark()
11184     * @apiName fetchBookmark
11185     * @apiGroup API
11186     * @apiDescription Fetch bookmark data
11187     * @apiSampleRequest off
11188     * 
11189     * @apiBody {String} lesson_identifier The lesson identifier.
11190     * @apiBody {String} textbook_connect_id The textbook connect ID.
11191     * 
11192     * @apiSuccess {Boolean} status2 The status of the request.
11193     * 
11194     * @apiErrorExample {json} Success-Response:
11195     *     {
11196     *       "status2": true,
11197     *     }
11198     *
11199      * @apiErrorExample {js} Used in: Ajax
11200     * Location: "view/HtmlTextBook/index.php"
11201     */
11202    public function fetchBookmark() {
11203        $this->autoRender = false;
11204        $response = array('status2' => false);
11205
11206        if($this->request->is('post')){
11207            $lesson_identifier = (isset($this->request->data['lesson_identifier'])) ? $this->request->data['lesson_identifier'] : null;
11208            $textbook_category_id = (isset($this->request->data['textbook_category_id'])) ? $this->request->data['textbook_category_id'] : null;
11209            if (!empty($lesson_identifier) && is_numeric($lesson_identifier)){
11210                $response = $this->LessonBookmark->getBookmarkviaUserId($lesson_identifier, $textbook_category_id);
11211            } else {
11212                $response = $this->LessonBookmark->getBookmarkviaChatHash($lesson_identifier, $textbook_category_id);
11213            }
11214            
11215        }
11216        return json_encode($response);
11217    }
11218
11219    public function fetchBookmarkTextbook() {
11220        $this->autoRender = false;
11221        $bookmarkInfo = array();
11222
11223        if($this->request->is('post')){
11224            $user_id = (isset($this->request->data['user_id'])) ? $this->request->data['user_id'] : null;
11225            $textbook_category_id = (isset($this->request->data['textbook_category_id'])) ? $this->request->data['textbook_category_id'] : null;
11226            
11227            $this->LessonBookmark->openDBReplica();
11228            $bookmark = $this->LessonBookmark->find('first', array(
11229                'fields' => array(
11230                    'Textbook.chapter_id',
11231                    'LessonBookmark.textbook_connect_id',
11232                    'TextbookConnect.category_id',
11233                    'TextbookConnect.subcategory_id',
11234                    'TextbookCategory.type_id',
11235                    'TextbookCategory.textbook_category_type',
11236                ),
11237                'joins' => array(
11238                    array(
11239                        'type' => 'LEFT',
11240                        'alias' => 'TextbookConnect',
11241                        'table' => 'textbook_connects',
11242                        'conditions' => 'TextbookConnect.id = LessonBookmark.textbook_connect_id'
11243                    ),
11244                    array(
11245                        'type' => 'LEFT',
11246                        'alias' => 'TextbookCategory',
11247                        'table' => 'textbook_categories',
11248                        'conditions' => 'TextbookCategory.id = TextbookConnect.category_id'
11249                    ),
11250                    array(
11251                        'type' => 'LEFT',
11252                        'alias' => 'Textbook',
11253                        'table' => 'textbooks',
11254                        'conditions' => 'Textbook.id = TextbookConnect.textbook_id'
11255                    )
11256                ),
11257                'conditions' => array(
11258                    'LessonBookmark.user_id' => $user_id,
11259                    'LessonBookmark.teacher_id' => $this->Auth->user('id'),
11260                    'TextbookConnect.category_id' => $textbook_category_id,
11261                ),
11262                // get most recent bookmark from the series/course
11263                'order' => array('LessonBookmark.modified DESC'),
11264                'recursive' => -1
11265            ));
11266            $this->LessonBookmark->closeDBReplica();
11267
11268            if ($bookmark) {
11269                $bookmarkInfo = array(
11270                    'connect_id' => $bookmark['LessonBookmark']['textbook_connect_id'],
11271                    'chapter_id' => $bookmark['Textbook']['chapter_id'],
11272                    'type_id' => $bookmark['TextbookCategory']['type_id'],
11273                    'category_type' => $bookmark['TextbookCategory']['textbook_category_type'],
11274                    'category_id' => $bookmark['TextbookConnect']['category_id'],
11275                    'subcategory_id' => $bookmark['TextbookConnect']['subcategory_id'],
11276                );
11277            }
11278            
11279        }
11280        return json_encode($bookmarkInfo);
11281    }
11282
11283    /**
11284     * @api {post} /teacher/api/saveNewCallanLevelCheck saveNewCallanLevelCheck()
11285     * @apiName saveNewCallanLevelCheck
11286     * @apiGroup API
11287     * @apiDescription Save new callan level check
11288     * @apiSampleRequest off
11289     * 
11290     * @apiBody {String} chat_hash The chat hash.
11291     * @apiBody {String} stage The stage.
11292     * 
11293     * @apiSuccess {Boolean} status The status of the request.
11294     * @apiSuccess {Array} data The data. (`$this->CallanBookmarkOption->find`)
11295     * 
11296     * @apiErrorExample {json} Success-Response:
11297     *     {
11298     *       "status": true,
11299     *       "data": {...}
11300     *     }
11301     *
11302     * @apiErrorExample {json} Error-Response:
11303     *     {
11304     *       "status": false,
11305     *       "error": {
11306     *           "message": "Cannot locate Callan Lesson Data"
11307     *       }
11308     *     }
11309     * 
11310      * @apiErrorExample {js} Used in: Ajax
11311     * Location: "view/HtmlTextBook/index.php"
11312     */
11313    public function saveNewCallanLevelCheck(){
11314        $this->autoRender = false;
11315        $response = array('status' => false);
11316
11317        if($this->request->is('post')){
11318            $chatHash = (isset($this->request->data['chat_hash'])) ? $this->request->data['chat_hash'] : null;
11319            $stage = (isset($this->request->data['stage'])) ? $this->request->data['stage'] : null;
11320            $category_id = isset($this->request->data['category_id']) ? $this->request->data['category_id'] : null;
11321            
11322            // get student and teacher IDs
11323            $this->LessonOnair->openDBReplica();
11324            $onairData = $this->LessonOnair->find('first', array(
11325                'fields' => array(
11326                    'LessonOnair.user_id',
11327                    'LessonOnair.teacher_id',
11328                    'LessonOnair.connect_id',
11329                ),
11330                'conditions' => array('chat_hash' => $chatHash),
11331                'recursive' => -1
11332            ));
11333            $this->LessonOnair->closeDBReplica();
11334
11335            // validate callan lesson
11336            // TODO: add logic for outside lesson
11337            if(empty($onairData)){
11338
11339                $this->LessonOnairsLog->openDBReplica();
11340                $onairLogData = $this->LessonOnairsLog->find('first', array(
11341                    'fields' => array(
11342                        'LessonOnairsLog.user_id',
11343                        'LessonOnairsLog.teacher_id',
11344                        'LessonOnairsLog.end_time',
11345                        'LessonOnairsLog.connect_id'
11346                    ),
11347                    'conditions' => array('chat_hash' => $chatHash),
11348                    'recursive' => -1
11349                ));
11350                $this->LessonOnairsLog->closeDBReplica();
11351
11352                if(empty($onairLogData)){
11353                    $response['error']['message'] = "Cannot locate Callan Lesson Data";
11354                    return json_encode($response); 
11355                }
11356
11357                // if greater than 3 minutes after lesson end
11358                $endTimeTimestamp = strtotime($onairLogData['LessonOnairsLog']['end_time']);
11359                $check3MinAfteLesson = (strtotime('now') <= $endTimeTimestamp + (3 * 60)) ? true : false; 
11360                if (!$check3MinAfteLesson) {
11361                    $response['error']['message'] = "Cannot bookmark after 3 minutes of lesson end";
11362                    return json_encode($response); 
11363                }
11364
11365                $user_id = $onairLogData['LessonOnairsLog']['user_id'];
11366                $connect_id = $onairLogData['LessonOnairsLog']['connect_id'] ?? null;
11367            } else {
11368                $user_id = $onairData['LessonOnair']['user_id'];
11369                $connect_id = $onairData['LessonOnair']['connect_id'] ?? null;
11370            }
11371
11372            if(!isset($connect_id)){
11373                $response['error']['message'] = "connect_id is empty";
11374                return json_encode($response); 
11375            }
11376
11377
11378            $saveParams = array(
11379                'stageID' =>  "Stage 0",
11380                'user_id' => $user_id,
11381                'level_check_stage' => $stage
11382            );
11383
11384            $this->UserNewLessonCallanProgress->saveStartDate($saveParams);
11385            
11386            $this->User->clear();
11387            $this->User->read(array('new_callan_level_check'), $user_id);
11388            $this->User->validate = false;
11389            $this->User->set(array('new_callan_level_check' =>  Configure::read('callan_entry_level.finished')));
11390            if($this->User->save()){
11391                $response['status'] = true;
11392            }
11393
11394            // update lesson bookmark
11395            $this->CallanBookmarkOption->openDBReplica();
11396            $bookmark = $this->CallanBookmarkOption->find('first', array(
11397                'fields' => array(
11398                    'id',
11399                    'stage',
11400                    'page',
11401                    'type',
11402                    'headword',
11403                    'paragraph',
11404                ),
11405                'conditions' => array('stage' => $stage),
11406                'order' => 'id ASC',
11407                'recursive' => -1
11408            ));
11409            $this->CallanBookmarkOption->closeDBReplica();
11410            
11411            if(isset($bookmark['CallanBookmarkOption']['stage'])){
11412                $this->CallanBookmarkOption->openDBReplica();
11413                $getTypeFormat = $this->CallanBookmarkOption->find('first', array(
11414                    'fields' => array(
11415                        "page", "type", "GROUP_CONCAT(paragraph ORDER BY paragraph SEPARATOR '|')  as paragraph"
11416                    ),
11417                    'conditions' => array(
11418                        'stage' => $bookmark['CallanBookmarkOption']['stage'],
11419                        'page' => $bookmark['CallanBookmarkOption']['page']
11420                    ),
11421                    'group' => 'page',
11422                    'recursive' => -1
11423                ));
11424                $this->CallanBookmarkOption->closeDBReplica();
11425            }
11426            
11427            // NJ-33801 - Change Type Format after bookmark
11428            $paragraphsInfo = !empty($getTypeFormat) ? explode('|', $getTypeFormat[0]['paragraph']) : null;
11429            
11430            if(!empty($paragraphsInfo)){
11431                $paragraphsInfo = explode('|', $getTypeFormat[0]['paragraph']);                
11432                $first = $paragraphsInfo[0];
11433                $last = end($paragraphsInfo);
11434                
11435                preg_match('/Lesson\s*(\d+)\s*,\s*(?:pp\s*(\d+)|(\d+))/i', $first, $firstMatch);
11436                preg_match('/Lesson\s*(\d+)\s*,\s*(?:pp\s*(\d+)|(\d+))/i', $last, $lastMatch);
11437                
11438                $lessonStart = $firstMatch[1];
11439                $lessonEnd = $lastMatch[1];
11440                $pageStart = $firstMatch[2];
11441                $pageEnd = $lastMatch[2];
11442                
11443                $lessonRange = ($lessonStart === $lessonEnd) ? "Lesson $lessonStart" : "Lesson $lessonStart~$lessonEnd";
11444                
11445                $pageRange = ($pageStart === $pageEnd) ? "pp$pageStart" : "pp$pageStart~$pageEnd";
11446            
11447                $formattedString = "".$bookmark['CallanBookmarkOption']['type']." ($lessonRange$pageRange)";
11448                
11449                $bookmark['CallanBookmarkOption']['formattedtype'] = $formattedString;    
11450            }else{
11451                $response['error']['message'] = "Paragraph Information is empty";
11452                return json_encode($response);
11453            }
11454
11455            // ~ if save status = true, return the bookmark data if exist
11456            if ($response['status'] && isset($bookmark['CallanBookmarkOption'])) {
11457                $bookmark['CallanBookmarkOption']['category_id'] = $category_id;
11458                $response['data'] = $bookmark['CallanBookmarkOption'];
11459            }
11460
11461            $this->LessonBookmark->openDBReplica();
11462            $currentBookmark = $this->LessonBookmark->find('first', array(
11463                'fields' => array(
11464                    'id',
11465                    'callan_bookmark_id'
11466                ),
11467                'conditions' => array('user_id' => $user_id),
11468                'recursive' => -1
11469            ));
11470            $this->LessonBookmark->closeDBReplica();
11471
11472            // Temporary value for Stage 3-12
11473            // todo: update after Stage 3-12 release
11474            // $bookmark_id = 0;
11475
11476            // if ($bookmark) {
11477            $bookmark_id = $bookmark['CallanBookmarkOption']['id'];
11478            // }
11479            
11480            $new_callan_category_ids = Configure::read('new_callan_textbook_category_id');
11481            $seres_textbook_connect_id = $this->getStageConnectID($stage, $new_callan_category_ids[0]);
11482            $course_textbook_connect_id = $this->getStageConnectID($stage, $new_callan_category_ids[1]);
11483            
11484            $bookmarkData = [];
11485                
11486            if (!empty($seres_textbook_connect_id)) {
11487                $bookmarkData[] = array(
11488                    'chat_hash' => $chatHash,
11489                    'textbook_connect_id' => $seres_textbook_connect_id,
11490                    'callan_bookmark_id' => $bookmark_id,
11491                    'user_id' => $user_id,
11492                    'teacher_id' => $this->Auth->user('id'),
11493                    'type' => 1 // callan
11494                );
11495            }
11496            
11497            if (!empty($course_textbook_connect_id)) {
11498                $bookmarkData[] = array(
11499                    'chat_hash' => $chatHash,
11500                    'textbook_connect_id' => $course_textbook_connect_id,
11501                    'callan_bookmark_id' => $bookmark_id,
11502                    'user_id' => $user_id,
11503                    'teacher_id' => $this->Auth->user('id'),
11504                    'type' => 1 // callan
11505                );
11506            }
11507
11508            $this->LessonBookmark->clear();
11509            if ($currentBookmark) {
11510                $this->LessonBookmark->query("Update lesson_bookmarks set callan_bookmark_id = $bookmark_id, textbook_connect_id = $seres_textbook_connect_id where user_id = $user_id and textbook_connect_id IN (SELECT id FROM textbook_connects where category_id = $new_callan_category_ids[0])");
11511                $this->LessonBookmark->query("Update lesson_bookmarks set callan_bookmark_id = $bookmark_id, textbook_connect_id = $course_textbook_connect_id where user_id = $user_id and textbook_connect_id IN (SELECT id FROM textbook_connects where category_id = $new_callan_category_ids[1])");
11512            } else if (!empty($bookmarkData)){
11513                try {
11514                    $this->LessonBookmark->create();
11515                    $this->LessonBookmark->saveAll($bookmarkData);
11516                } catch (Exception $e) {
11517                    $this->log("failed to save new bookmark-> " . json_encode($bookmarkData), "debug");
11518                }
11519            }
11520
11521            //if level Check test save the stage number
11522            $saveData = array(
11523                'chat_hash' => $chatHash,
11524                'textbook_connect_id' => $connect_id,
11525                'callan_bookmark_id' => $bookmark_id,
11526                'user_id' => $user_id,
11527                'teacher_id' => $this->Auth->user('id'),
11528                'type' => 1 // callan
11529            );
11530
11531            // save to lesson bookmark history as well
11532            $this->LessonBookmarkHistory->openDBReplica();
11533            $currentBookmarkHistory = $this->LessonBookmarkHistory->find('first', array(
11534                'fields' => array(
11535                    'id',
11536                    'callan_bookmark_id'
11537                ),
11538                'conditions' => array('user_id' => $user_id, 'chat_hash' => $chatHash),
11539                'recursive' => -1
11540            ));
11541            $this->LessonBookmarkHistory->closeDBReplica();
11542            
11543            $this->LessonBookmarkHistory->clear();
11544            if ($currentBookmarkHistory) {
11545                $this->LessonBookmarkHistory->read(array_keys($saveData), $currentBookmarkHistory['LessonBookmarkHistory']['id']);
11546            }
11547            $this->LessonBookmarkHistory->set($saveData);
11548            if (!$this->LessonBookmarkHistory->save()){
11549                $this->log('[NJ-33115] failed to bookmark history' . json_encode($saveData) , 'debug');
11550            }
11551            
11552            
11553        }
11554    
11555        return json_encode($response); 
11556    }
11557
11558    private function getStageConnectID($stage, $category_id = null){
11559        $conditions = array(
11560            'Textbook.name LIKE' => "%Stage ".$stage."%"
11561        );
11562        
11563        if (!empty($category_id)) {
11564            $conditions['TextbookConnect.category_id'] = $category_id;
11565        }
11566        
11567        
11568        $this->loadModel("Textbook");
11569        $this->Textbook->openDBReplica();
11570        $textbookData = $this->Textbook->find('first', array(
11571            'fields' => array(
11572                'TextbookConnect.id'
11573            ),
11574            'joins' => array(
11575                array(
11576                    'table' => 'textbook_connects',
11577                    'alias' => 'TextbookConnect',
11578                    'type' => 'INNER',
11579                    'conditions' => array(
11580                        'TextbookConnect.textbook_id = Textbook.id'
11581                    )
11582                )
11583            ),
11584            'conditions' => $conditions,
11585            'recursive' => -1
11586        ));
11587        $this->Textbook->closeDBReplica();
11588
11589        if (!empty($textbookData)) {
11590            return $textbookData['TextbookConnect']['id'];
11591        }
11592
11593        return 0;
11594    }
11595
11596    /**
11597     * @api {post} /teacher/api/recordPlayedPhrase recordPlayedPhrase()
11598     * @apiName recordPlayedPhrase
11599     * @apiGroup API
11600     * @apiDescription Record played phrase
11601     * @apiSampleRequest off
11602     * 
11603     * @apiBody {String} avatar_phase_id The avatar phrase ID.
11604     * 
11605     * @apiSuccess {Boolean} success The status of the request.
11606     * 
11607     * @apiErrorExample {json} Success-Response:
11608     *     {
11609     *       "success": true
11610     *     }
11611     *
11612      * @apiErrorExample {js} Used in: Ajax
11613     * Location: "view/Home/index.php"
11614     */
11615    public function recordPlayedPhrase(){
11616        $this->autoRender = false;
11617        $teacherId = $this->Auth->user('id');
11618        $avatarPhraseId = $this->request->data['avatar_phase_id'] ?? null;
11619        $onAirModel = 'LessonOnair';
11620        $chatHash = $this->getCurrentChatHash($onAirModel, $teacherId);
11621        $this->Session->write('chatHash', $chatHash);
11622        $this->loadModel('AvatarPhraseRecord');
11623    
11624        if (!$teacherId || !$avatarPhraseId) {
11625            return json_encode(['success' => false]);
11626        }
11627
11628        // check existing record
11629        $this->AvatarPhraseRecord->openDBReplica();
11630        $existRecord = $this->AvatarPhraseRecord->find('first', [
11631            'fields' => ['AvatarPhraseRecord.id'],
11632            'conditions' => [
11633                'AvatarPhraseRecord.teacher_id' => $teacherId,
11634                'AvatarPhraseRecord.avatar_phrase_id' => $avatarPhraseId,
11635                'AvatarPhraseRecord.chat_hash' => $chatHash
11636            ],
11637        ]);
11638        $this->AvatarPhraseRecord->closeDBReplica();
11639        
11640        if ($existRecord) {
11641            $this->AvatarPhraseRecord->query("UPDATE avatar_phrase_records SET total = total + 1 WHERE id = {$existRecord['AvatarPhraseRecord']['id']}");
11642            return json_encode(['success' => true]);
11643        } else {
11644            // insert new record
11645            $this->AvatarPhraseRecord->create();
11646            $this->AvatarPhraseRecord->set([
11647                'teacher_id' => $teacherId,
11648                'avatar_phrase_id' => $avatarPhraseId,
11649                'total' => 1,
11650                'chat_hash' => $chatHash
11651            ]);
11652            return json_encode(['success' => $this->AvatarPhraseRecord->save() ? true : false]);
11653        }
11654    }
11655
11656    /**
11657     * @api {post} /teacher/api/gameGetPlayersProfile gameGetPlayersProfile()
11658     * @apiName gameGetPlayersProfile
11659     * @apiGroup API
11660     * @apiDescription Get players profile
11661     * @apiSampleRequest off
11662     * 
11663     * @apiBody {String} user_id The user ID.
11664     * @apiBody {String} teacher_id The teacher ID.
11665     * 
11666     * @apiSuccess {Boolean} error The status of the request.
11667     * @apiSuccess {Array} profile_data The profile data.
11668     * @apiSuccess {Array} profile_data.user The user profile data.
11669     * @apiSuccess {Array} profile_data.teacher The teacher profile data.
11670     * @apiSuccess {Number} profile_data.user.id The user ID.
11671     * @apiSuccess {String} profile_data.user.profile_img The user profile image URL.
11672     * @apiSuccess {Number} profile_data.teacher.id The teacher ID.
11673     * @apiSuccess {String} profile_data.teacher.profile_img The teacher profile image URL.
11674     * 
11675     * @apiErrorExample {json} Success-Response:
11676     *     {
11677     *       "error": false,
11678     *       "profile_data": {
11679     *           "user": {
11680     *               "id": 1,
11681     *               "profile_img": "url"
11682     *           },
11683     *           "teacher": {
11684     *               "id": 1,
11685     *               "profile_img": "url"
11686     *           }
11687     *       }
11688     *     }
11689     *
11690     * @apiErrorExample {json} Error-Response:
11691     *     {
11692     *       "error": true,
11693     *       "profile_data": [],
11694     *       "err_msg": "Invalid params"
11695     *     }
11696     * 
11697     * @apiErrorExample {js} Used in: Elements
11698     * Location: "view/Elements/chat_area_js.php"
11699     */
11700    public function gameGetPlayersProfile(){
11701        $this->autoRender = false;
11702        $res = [
11703            'error' => true,
11704            'profile_data' => []
11705        ];
11706        # check if the request is pot
11707        if($this->request->is('post')){
11708            $data = $this->request->data;
11709            $user_id = $data['user_id'] ?? null;
11710            $teacher_id = $data['teacher_id'] ?? null;
11711
11712            if(!$user_id || !$teacher_id){
11713                $res['err_msg'] = 'Invalid params';
11714                return json_encode($res);
11715            }
11716
11717            $user_data = $this->User->getUserData(
11718                ['User.id' => $user_id], // - condition
11719                ['*'], // - fields
11720                'first' // - type
11721            );
11722
11723            $tacher_data = $this->Teacher->getTeacherInfo($teacher_id);
11724
11725            if(!$user_data || !$tacher_data){
11726                $res['err_msg'] = 'Invalid params';
11727                return json_encode($res);
11728            }
11729
11730            $user_obj = new UserTable($user_data['User']);
11731            $teacher_obj = new TeacherTable($tacher_data['Teacher']);
11732
11733            $res['profile_data'] = [
11734                'user' => [
11735                    'id' => (int)$user_obj->id, 
11736                    'profile_img' => $user_obj->getImageUrl()
11737                ],
11738                'teacher' => [
11739                    'id' => (int)$teacher_obj->id, 
11740                    'profile_img' => $teacher_obj->getImageUrl()
11741                ],
11742            ];
11743
11744            $res['error'] = false;
11745
11746        }
11747        return json_encode($res);
11748    }
11749
11750    /**
11751     * @api {post} /teacher/api/saveGameResult saveGameResult()
11752     * @apiName saveGameResult
11753     * @apiGroup API
11754     * @apiDescription Save game result
11755     * @apiSampleRequest off
11756     * 
11757     * @apiBody {String} user_id The user ID.
11758     * @apiBody {String} teacher_id The teacher ID.
11759     * @apiBody {String} teacher_score The teacher score.
11760     * @apiBody {String} student_score The student score.
11761     * @apiBody {String} winner The winner.
11762     * @apiBody {String} end_time The end time.
11763     * @apiBody {String} textbook_id The textbook ID.
11764     * @apiBody {String} chat_hash The chat hash.
11765     * 
11766     * @apiSuccess {Boolean} error The status of the request.
11767     * @apiSuccess {Array} game_result The game result data.
11768     * @apiSuccess {Number} game_result.user_id The user ID.
11769     * @apiSuccess {Number} game_result.teacher_id The teacher ID.
11770     * @apiSuccess {Number} game_result.student_score The student score.
11771     * @apiSuccess {Number} game_result.teacher_score The teacher score.
11772     * @apiSuccess {Number} game_result.end_time The end time.
11773     * @apiSuccess {Number} game_result.textbook_id The textbook ID.
11774     * @apiSuccess {Number} game_result.teacher_result The teacher result.
11775     * 
11776     * @apiErrorExample {json} Success-Response:
11777     *     {
11778     *       "error": false,
11779     *       "game_result": {
11780     *              "user_id": 1,
11781     *              "teacher_id": 1,
11782     *              "student_score": 1,
11783     *              "teacher_score": 1,
11784     *              "end_time": "2021-09-01 00:00:00",
11785     *              "textbook_id": 1,
11786     *              "teacher_result": 1
11787     *              }
11788     *     }
11789     *
11790     * @apiErrorExample {json} Error-Response:
11791     *     {
11792     *       "error": true,
11793     *       "game_result": []
11794     *     }
11795     */
11796    public function saveGameResult(){
11797        $this->autoRender = false;
11798        $res = [
11799            'error' => true,
11800            'game_result' => []
11801        ];
11802        # check if the request is post
11803        if($this->request->is('post')){
11804            $data = $this->request->data;
11805            $user_id = $data['user_id'] ?? null;
11806            $teacher_id = $data['teacher_id'] ?? null;
11807            $teacher_score = $data['teacher_score'] ?? 0;
11808            $student_score = $data['student_score'] ?? 0;
11809            $winner = $data['winner'] ?? 0;
11810            $end_time = $data['end_time'] ?? null;
11811            $textbook_id = $data['textbook_id'] ?? null;
11812            $chatHash = $data['chat_hash'] ?? null;
11813
11814            $game_result_data = [
11815                'user_id' => $user_id,
11816                'teacher_id' => $teacher_id,
11817                'student_score' => $student_score,
11818                'teacher_score' => $teacher_score,
11819                'end_time' => $end_time,
11820                'textbook_id' => $textbook_id,
11821            ];
11822
11823            // - draw
11824            if($winner == 0){
11825                $game_result_data['teacher_result'] = Configure::read('game_results.draw');
11826                $game_result_data['student_result'] = Configure::read('game_results.draw');
11827            
11828            // - student wins
11829            } elseif ($winner == 1) {
11830                $game_result_data['student_result'] = Configure::read('game_results.win');
11831                $game_result_data['teacher_result'] = Configure::read('game_results.loss');
11832            
11833            // - teacher wins
11834            } elseif ($winner == 2) {
11835                $game_result_data['teacher_result'] = Configure::read('game_results.win');
11836                $game_result_data['student_result'] = Configure::read('game_results.loss');
11837            }
11838
11839            // - save game result
11840            $gs_model = ClassRegistry::init('GameResult');
11841            $gs_model->clear();
11842            $gs_model->set($game_result_data);
11843            if(!$gs_model->save()){
11844                $res['msg'] = 'Error saving game result';
11845            } else {
11846                $res['error'] = false;
11847
11848                if (!class_exists('myMemcached')) {
11849                    App::uses('myMemcached', 'Lib');
11850                }
11851                $game_mem = new myMemcached();
11852                // delete memcache game
11853                if($game_mem->get($chatHash)){
11854                    $game_mem->delete($chatHash);
11855                }
11856            }
11857            $res['game_result'] = $game_result_data;
11858        }
11859        return json_encode($res);
11860    }
11861
11862    /**
11863     * @api {post} /teacher/api/saveGameDataMemcache saveGameDataMemcache()
11864     * @apiName saveGameDataMemcache
11865     * @apiGroup API
11866     * @apiDescription Save game data to memcache
11867     * @apiSampleRequest off
11868     * 
11869     * @apiSuccess {String} message Success message
11870     * @apiSuccess {Object} data Data returned from the API
11871     * @apiSuccess {Number} data.id Unique identifier of the resource
11872     * @apiSuccess {String} data.name Name of the resource
11873     * @apiSuccess {String} data.description Description of the resource
11874     * @apiSuccess {String} data.created_at Timestamp when the resource was created
11875     * @apiSuccess {String} data.updated_at Timestamp when the resource was last updated
11876     * 
11877     * 
11878     * @apiErrorExample {json} Success-Response:
11879     *     {
11880     *       "error": false,
11881     *       "game_data": {
11882     *              "start": 1,
11883     *              "first_turn": 1,
11884     *              "cardSetNo": 1,
11885     *              "player_turn": 1,
11886     *              "scores": {
11887     *                  "student_score": 1,
11888     *                  "teacher_score": 1
11889     *              },
11890     *              "cards": [
11891     *                  {
11892     *                      "card_no": 1,
11893     *                      "animal": "animal",
11894     *                      "flipped": 1,
11895     *                      "show": 1,
11896     *                      "match_card_no": 1
11897     * 
11898     *       }
11899     *     }
11900     *
11901     * @apiErrorExample {json} Error-Response:
11902     *     {
11903     *       "error": true,
11904     *       "game_data": []
11905     *     }
11906     * 
11907     * @apiErrorExample {js} Used in: Elements
11908     * Location: "view/Elements/chat_area_js.php"
11909     */
11910    public function saveGameDataMemcache(){
11911        $this->autoRender = false;
11912        $res = [
11913            'error' => true,
11914            'game_data' => []
11915        ];
11916
11917        # check if the request is pot
11918        if($this->request->is('post')){
11919            $data = $this->request->data;
11920            $res['params'] = $data;
11921            $chatHash = $data['chat_hash'] ?? null;
11922            $action = $data['action'] ?? null;
11923            $params = $data['params'] ?? null;
11924            $teacherID = $data['teacher_id'] ?? null;
11925            $userID = $data['user_id'] ?? null;
11926
11927            if(!$chatHash){ return $res; }
11928
11929            // - use memcached
11930            if (!class_exists('myMemcached')) {
11931                App::uses('myMemcached', 'Lib');
11932            }
11933            $game_mem = new myMemcached();
11934            $game_mem_key = $chatHash;
11935            $game_cache = $game_mem->get($game_mem_key);
11936            $game_data = $game_cache ?? [];
11937            switch ($action) {
11938                case 'start':
11939                    $game_data['start'] = (int)1;
11940                    break;
11941
11942                case 'first_turn':
11943                    $game_data['first_turn'] = $params['player_turn'] = $params['first_turn'] ?? null;
11944                    break;
11945                
11946                case 'card_set':
11947                    $game_data['cardSetNo'] = (int)$params['cardSetNo'] ?? 0;
11948                    break;
11949
11950                case 'player_turn':
11951                    $game_data['player_turn'] = (int)$params['player_turn'] ?? 0;
11952                    break;
11953                
11954                case 'scores':
11955                    $game_data['scores'] = [
11956                        'student_score' => (int)$params['student_score'] ?? 0,
11957                        'teacher_score' => (int)$params['teacher_score'] ?? 0,
11958                    ];
11959                    break;
11960
11961                case 'animal_cards':
11962                    $game_data['cards'] = $params['animal_cards'] ?? [];
11963                    break;
11964
11965                case 'cards_match':
11966                    if(
11967                        !empty($params['selected_cards']) 
11968                        && isset($params['is_match'])
11969                    ){
11970                        $selected_cards = $params['selected_cards'];
11971                        $selected_card1 = $selected_cards['selected_card_1'];
11972                        $selected_card2 = $selected_cards['selected_card_2'];
11973                        $card_selected_idx1 = myTools::getArrayIndexBySearchingColumnValue($game_data['cards'], 'card_no', $selected_card1);
11974                        $card_selected_idx2 = myTools::getArrayIndexBySearchingColumnValue($game_data['cards'], 'card_no', $selected_card2);
11975
11976                        if($params['is_match'] == 1){
11977                            $game_data['cards'][$card_selected_idx1]['show'] =  0;
11978                            $game_data['cards'][$card_selected_idx1]['match_card_no'] =  $selected_card2;
11979                            $game_data['cards'][$card_selected_idx2]['show'] =  0;
11980                            $game_data['cards'][$card_selected_idx2]['match_card_no'] =  $selected_card1;
11981
11982                        } else {
11983                            $game_data['cards'][$card_selected_idx1]['flipped'] = 0;
11984                            $game_data['cards'][$card_selected_idx2]['flipped'] = 0;
11985                            
11986                        }
11987                        
11988                        $game_data['player_moves'] = 0;
11989                        $game_data['selected_card1_id'] = 0;
11990                        $game_data['selected_card2_id'] = 0;
11991                        $game_data['selected_card1'] = null;
11992                        $game_data['selected_card2'] = null;
11993                    }
11994                    
11995                    break;
11996                    
11997                case 'flip_card':
11998                    $card_number_flip = $params['card_no'] ?? 0;
11999                    $player_moves = (int)$params['player_moves'] ?? 0;
12000                    
12001                    if(!$card_number_flip){ break; }
12002
12003                    $cards_data = $game_data['cards'] ?? [];
12004                    if($cards_data){
12005                        $card_idx = myTools::getArrayIndexBySearchingColumnValue($cards_data, 'card_no', $card_number_flip);
12006                        $cards_data[$card_idx]['flipped'] = 1;
12007                        $cards_data[$card_idx]['match_card_no'] = 0;
12008                    }
12009
12010                    if($player_moves == 1){
12011                        $game_data['selected_card1_id'] = (int)$card_number_flip;
12012                        $game_data['selected_card1'] = $cards_data[$card_idx]['animal'];
12013                    } elseif($player_moves == 2){
12014                        $game_data['selected_card2_id'] = (int)$card_number_flip;
12015                        $game_data['selected_card2'] = $cards_data[$card_idx]['animal'];
12016                    }
12017                    
12018                    $game_data['player_moves'] = $player_moves;
12019                    $game_data['cards'] = $cards_data;
12020                    break;
12021
12022                case 'players_profile':
12023                    $game_data['players_profile'] = $params['players_profile'];
12024                    $game_data['chat_hash'] = $params['chat_hash'] ?? null;
12025                    $game_data['start_time'] = $params['start_time'] ?? null;
12026                    $game_data['teacher_id'] = $params['teacher_id'] ?? null;
12027                    $game_data['user_id'] = $params['user_id'] ?? null;
12028
12029                    break;
12030            }
12031
12032            // - update game cache data
12033            if($game_cache){
12034                $game_mem->set(array(
12035                    'key' => $game_mem_key,
12036                    'value' => $game_data,
12037                ));
12038
12039            //- create game cache data
12040            } else {
12041                $game_mem->set(array(
12042                    'key' => $game_mem_key,
12043                    'value' => $game_data,
12044                    'expire' => 1800
12045                ));
12046            }
12047
12048            $res['error'] = false;
12049            $res['game_data'] = $game_data;
12050            return json_encode($res);
12051        }
12052
12053    }
12054
12055    /**
12056     * @api {post} /teacher/api/gameDataMemcache gameDataMemcache()
12057     * @apiName gameDataMemcache
12058     * @apiGroup API
12059     * @apiDescription Get game data from memcache
12060     * @apiSampleRequest off
12061     * 
12062     * @apiBody {String} chat_hash The chat hash.
12063     * @apiBody {String} action The action to be performed (e.g., start, first_turn, card_set, player_turn, scores, animal_cards, cards_match, flip_card, players_profile).
12064      * 
12065     * @apiSuccess {Boolean} error Indicates if there was an error.
12066      * @apiSuccess {Object} game_data The game data stored in Memcache.
12067      * @apiSuccess {Object} game_data.start Indicates if the game has started.
12068      * @apiSuccess {Number} game_data.first_turn The first turn player.
12069      * @apiSuccess {Number} game_data.cardSetNo The card set number.
12070      * @apiSuccess {Number} game_data.player_turn The current player's turn.
12071      * @apiSuccess {Object} game_data.scores The scores of the players.
12072      * @apiSuccess {Number} game_data.scores.student_score The student's score.
12073      * @apiSuccess {Number} game_data.scores.teacher_score The teacher's score.
12074      * @apiSuccess {Object[]} game_data.cards The list of animal cards.
12075      * @apiSuccess {Number} game_data.cards.card_no The card number.
12076      * @apiSuccess {String} game_data.cards.animal The animal on the card.
12077      * @apiSuccess {Number} game_data.cards.flipped Indicates if the card is flipped.
12078      * @apiSuccess {Number} game_data.cards.match_card_no The matching card number.
12079      * @apiSuccess {Number} game_data.player_moves The number of player moves.
12080      * @apiSuccess {Number} game_data.selected_card1_id The ID of the first selected card.
12081      * @apiSuccess {String} game_data.selected_card1 The animal on the first selected card.
12082      * @apiSuccess {Number} game_data.selected_card2_id The ID of the second selected card.
12083      * @apiSuccess {String} game_data.selected_card2 The animal on the second selected card.
12084      * @apiSuccess {Object} game_data.players_profile The profiles of the players.
12085      * @apiSuccess {String} game_data.players_profile.teacher The teacher's name.
12086      * @apiSuccess {String} game_data.players_profile.student The student's name.
12087      * @apiSuccess {String} game_data.chat_hash The chat hash.
12088      * @apiSuccess {String} game_data.start_time The start time of the game.
12089      * @apiSuccess {Number} game_data.teacher_id The ID of the teacher.
12090      * @apiSuccess {Number} game_data.user_id The ID of the user.
12091      * @apiSuccess {Object} params The parameters sent in the request. 
12092      * @apiSuccess {String} params.chat_hash The chat hash.
12093      * @apiSuccess {String} params.action The action performed.
12094      * @apiSuccess {Object} params.params The parameters for the action.
12095      * @apiSuccess {Number} params.params.player_turn The current player's turn.
12096      * @apiSuccess {Number} params.params.first_turn The first turn player.
12097      * @apiSuccess {Number} params.params.cardSetNo The card set number.
12098      * @apiSuccess {Number} params.params.student_score The student's score.
12099      * @apiSuccess {Number} params.params.teacher_score The teacher's score.
12100      * @apiSuccess {Object[]} params.params.animal_cards The list of animal cards.
12101      * @apiSuccess {Number} params.params.animal_cards.card_no The card number.
12102      * @apiSuccess {String} params.params.animal_cards.animal The animal on the card.
12103      * @apiSuccess {Object} params.params.selected_cards The selected cards.
12104      * @apiSuccess {Number} params.params.selected_cards.selected_card_1 The first selected card number.
12105      * @apiSuccess {Number} params.params.selected_cards.selected_card_2 The second selected card number.
12106      * @apiSuccess {Number} params.params.is_match Indicates if the selected cards match.
12107      * @apiSuccess {Number} params.params.card_no The card number.
12108      * @apiSuccess {Number} params.params.player_moves The number of player moves.
12109      * @apiSuccess {Object} params.params.players_profile The profiles of the players.
12110      * @apiSuccess {String} params.params.players_profile.teacher The teacher's name.
12111      * @apiSuccess {String} params.params.players_profile.student The student's name.
12112      * @apiSuccess {String} params.params.chat_hash The chat hash.
12113      * @apiSuccess {String} params.params.start_time The start time of the game.
12114      * @apiSuccess {Number} params.params.teacher_id The ID of the teacher.
12115      * @apiSuccess {Number} params.params.user_id The ID of the user.
12116     * 
12117     * @apiErrorExample {json} Success-Response:
12118     * {
12119     *   "error": false,
12120     *   "game_data": {
12121     *     "start": 1,
12122     *     "first_turn": 1,
12123     *     "cardSetNo": 1,
12124     *     "player_turn": 1,
12125     *     "scores": {
12126     *       "student_score": 10,
12127     *       "teacher_score": 5
12128     *     },
12129     *     "cards": [
12130     *       {
12131     *         "card_no": 1,
12132     *         "animal": "Lion",
12133     *         "flipped": 1,
12134     *         "match_card_no": 0
12135     *       }
12136     *     ],
12137     *     "player_moves": 2,
12138     *     "selected_card1_id": 1,
12139     *     "selected_card1": "Lion",
12140     *     "selected_card2_id": 2,
12141     *     "selected_card2": "Tiger",
12142     *     "players_profile": {
12143     *       "teacher": "TeacherName",
12144     *       "student": "StudentName"
12145     *     },
12146     *     "chat_hash": "abc123",
12147     *     "start_time": "2023-10-01 10:00:00",
12148     *     "teacher_id": 123,
12149     *     "user_id": 456
12150     *   },
12151     *   "params": {
12152     *     "chat_hash": "abc123",
12153     *     "action": "start",
12154     *     "params": {
12155     *       "player_turn": 1,
12156     *       "first_turn": 1,
12157     *       "cardSetNo": 1,
12158     *       "student_score": 10,
12159     *       "teacher_score": 5,
12160     *       "animal_cards": [
12161     *         {
12162     *           "card_no": 1,
12163     *           "animal": "Lion"
12164     *         }
12165     *       ],
12166     *       "selected_cards": {
12167     *         "selected_card_1": 1,
12168     *         "selected_card_2": 2
12169     *       },
12170     *       "is_match": 1,
12171     *       "card_no": 1,
12172     *       "player_moves": 2,
12173     *       "players_profile": {
12174     *         "teacher": "TeacherName",
12175     *         "student": "StudentName"
12176     *       },
12177     *       "chat_hash": "abc123",
12178     *       "start_time": "2023-10-01 10:00:00",
12179     *       "teacher_id": 123,
12180     *       "user_id": 456
12181     *     }
12182     *   }
12183     * }
12184     *
12185      * @apiError {Boolean} error Indicates if there was an error.
12186      * @apiError {Object} game_data The game data stored in Memcache.
12187      * @apiError {Object} params The parameters sent in the request.
12188      *
12189      * @apiErrorExample {json} Error-Response:
12190      * {
12191      *   "error": true,
12192      *   "game_data": [],
12193      *   "params": {
12194      *     "chat_hash": "abc123",
12195      *     "action": "start",
12196      *     "params": {
12197      *       "player_turn": 1,
12198      *       "first_turn": 1,
12199      *       "cardSetNo": 1,
12200      *       "student_score": 10,
12201      *       "teacher_score": 5,
12202      *       "animal_cards": [
12203      *         {
12204      *           "card_no": 1,
12205      *           "animal": "Lion"
12206      *         }
12207      *       ],
12208      *       "selected_cards": {
12209      *         "selected_card_1": 1,
12210      *         "selected_card_2": 2
12211      *       },
12212      *       "is_match": 1,
12213      *       "card_no": 1,
12214      *       "player_moves": 2,
12215      *       "players_profile": {
12216      *         "teacher": "TeacherName",
12217      *         "student": "StudentName"
12218      *       },
12219      *       "chat_hash": "abc123",
12220      *       "start_time": "2023-10-01 10:00:00",
12221      *       "teacher_id": 123,
12222      *       "user_id": 456
12223      *     }
12224      *   }
12225      * }
12226     * 
12227     * @apiErrorExample {js} Used in: Elements
12228     * Location: "view/Elements/chat_area_js.php"
12229     */
12230    public function gameDataMemcache(){
12231        $this->autoRender = false;
12232        $res = [
12233            'error' => true,
12234            'game_data' => []
12235        ];
12236
12237        if($this->request->is('post')){
12238            $data = $this->request->data;
12239            $game_chatHash = $data['chat_hash'];
12240
12241            // - use memcached
12242            if (!class_exists('myMemcached')) {
12243                App::uses('myMemcached', 'Lib');
12244            }
12245            $game_mem = new myMemcached();
12246            $game_mem_key = $game_chatHash;
12247            $game_cache = $game_mem->get($game_mem_key);
12248            $game_data = $game_cache ?? [];
12249            $res['error'] = false;
12250            $res['game_data'] = $game_data;
12251        }
12252
12253        return json_encode($res);
12254    }
12255    
12256    //only used in this controller
12257    public function getCurrentChatHash($modelName = null, $teacher_id){
12258        $chatHash = '';
12259    
12260        // Check if the model exists and is not null
12261        if (isset($this->$modelName)) {
12262            $getCurrentChatHash = $this->$modelName->find('first', array(
12263                'fields' => array('chat_hash'),
12264                'conditions' => array("$modelName.teacher_id" => $teacher_id),
12265            ));
12266            
12267            // Check if result is not empty before accessing its elements
12268            if (!empty($getCurrentChatHash[$modelName]['chat_hash'])) {
12269                $chatHash = $getCurrentChatHash[$modelName]['chat_hash'];
12270            }
12271        }
12272        
12273        return $chatHash;
12274    }
12275
12276    /**
12277     * @api {get} /teacher/api/googleCalendarCb googleCalendarCb()
12278     * @apiName googleCalendarCb
12279     * @apiGroup API
12280     * @apiDescription Google Calendar Callback
12281     * @apiSampleRequest off
12282     * 
12283     * @apiErrorExample {js} Return Type: View
12284     * Location: "view/Api/googleCalendarCb.php"
12285     */
12286    /**
12287    * GOOGLE INTEGRATION - SAVE GENERATED ACCESS TOKEN
12288    */ 
12289    public function googleCalendarCb(){
12290        $this->autoRender = false;
12291        $apiToken             = $this->Session->read('googleauthTeacherToken');
12292        $isMobappSreen         = $this->Session->read('isMobappScreen');
12293        $mobappTeacherId     = $this->Session->read('googleauthTeacherId');
12294        $teacherId             = !empty($mobappTeacherId) ? $mobappTeacherId : $this->Auth->user('id');
12295
12296        if( !empty($this->request->query['code']) ) {
12297            $google             = new GoogleCalendar();
12298            $code                 = $this->request->query['code'];
12299            $deviceType         = $this->request->query['state'];
12300            $myEnv                 = Configure::read('ENVIRONMENT');
12301            $redirectUri         = Configure::read('GOOGLE_CALENDAR_REDIRECT_URI.TEACHER');
12302            $myEnvRedirectUri     = $redirectUri[$myEnv];
12303            $data                 = $google->GetAccessToken(Configure::read('GOOGLE_CLIENT_ID_CALENDAR'), $myEnvRedirectUri, Configure::read('GOOGLE_CLIENT_SECRET_CALENDAR'), $code);
12304            $newAccessToken     = $data['access_token'];
12305            $newRefreshToken     = $data['refresh_token'];
12306
12307            if ( $deviceType == 0 || !empty($isMobappSreen) ) {
12308                require ROOT. "/teacher/Controller/GoogleCalendarSettingController.php";        
12309                $googleCalendarSetting = new GoogleCalendarSettingController();
12310                $response['success'] = $googleCalendarSetting->addGoogleAccessToken(array(
12311                    'teacher_id'             => $teacherId,
12312                    'google_access_token'     => $newAccessToken,
12313                    'google_refresh_token'     => $newRefreshToken
12314                ));
12315                if ( !empty($isMobappSreen) ) {
12316                    $this->Teacher->read(['google_calendar_flg'], $teacherId);
12317                    $this->Teacher->set(array(
12318                        'google_calendar_flg' => 1
12319                    ));
12320                    $this->Teacher->save();
12321                    return $this->redirect(myTools::getTeacherUrl() . "/mobapp/schedule?token={$apiToken}" );
12322                }
12323                return $this->redirect(myTools::getUrl() . '/teacher/home?calendarOAuth=1');
12324
12325            } else{
12326                $data = array(
12327                    'access_token' => $newAccessToken,
12328                    'refresh_token' => $newRefreshToken
12329                );
12330                $schemeUrl = 'nativecamp://view/google_calendar_success?'.http_build_query($data,'','&');
12331                $schemeUrl = urldecode($schemeUrl);
12332                $this->set('schemeUrl', $schemeUrl);
12333            }
12334        }
12335        $this->render('/Api/googleCalendarCb');
12336    }
12337
12338    /**
12339     * @api {post} /teacher/api/googleCalendarFlagUpdate googleCalendarFlagUpdate()
12340     * @apiName googleCalendarFlagUpdate
12341     * @apiGroup API
12342     * @apiDescription Update teacher google calendar synch flag
12343     * @apiSampleRequest off
12344     * 
12345     * @apiBody {Number} calendar_flag The flag to indicate if the calendar is synched.
12346     * 
12347     * @apiSuccess {Boolean} success Indicates if the operation was successful.
12348     * 
12349     * @apiErrorExample {json} Success-Response:
12350     *     {
12351     *       "success": true
12352     *     }
12353     *
12354     * @apiErrorExample {json} Error-Response:
12355     *     {
12356     *       "success": false
12357     *     }
12358     * 
12359     * @apiErrorExample {js} Used in: View
12360     * Location: "view/Announce/index.php"
12361     * Location: "view/Home/index.php"
12362     * Location: "view/Mobapp/index.php"
12363     */
12364    /**
12365    * Update teacher google calendar synch flag 
12366    * @return mixed - response
12367    */ 
12368    public function googleCalendarFlagUpdate() {
12369        $this->autoRender = false;
12370        $response = array(
12371            'success' => false
12372        );
12373        $mobappTeacherId     = $this->Session->read('googleauthTeacherId');
12374        $teacherId             = !empty($mobappTeacherId) ? $mobappTeacherId : $this->Auth->user('id');
12375        if (
12376            $this->request->is('ajax')
12377            && isset($this->request->data['calendar_flag'])
12378            && !empty($teacherId)
12379        ) {
12380            require ROOT. "/teacher/Controller/GoogleCalendarSettingController.php";        
12381            $googleCalendarSetting = new GoogleCalendarSettingController();
12382            $response['success'] = $googleCalendarSetting->googleCalendarFlagUpdate(array(
12383                'teacher_id' => $teacherId,
12384                'google_calendar_flag' => !empty($this->request->data['calendar_flag']) ? 1 : 0
12385            ));
12386        }
12387        return json_encode($response);
12388    }
12389
12390    /**
12391     * @api {get} /teacher/api/unlinkGoogleCalendar unlinkGoogleCalendar()
12392     * @apiName unlinkGoogleCalendar
12393     * @apiGroup API
12394     * @apiDescription Unlink google calendar
12395     * @apiSampleRequest off
12396     * 
12397     * @apiSuccess {Boolean} success Indicates if the operation was successful.
12398     * 
12399     * @apiErrorExample {json} Success-Response:
12400     *     {
12401     *       "success": true
12402     *     }
12403     *
12404     * @apiErrorExample {json} Error-Response:
12405     *     {
12406     *       "success": false
12407     *     }
12408     * 
12409     * @apiErrorExample {js} Used in: View
12410     * Location: "view/Announce/index.php"
12411     * Location: "view/Home/index.php"
12412     * Location: "view/Mobapp/index.php"
12413     * 
12414      * @apiErrorExample {js} Used in: JS
12415      * Location: "webroot/js/common.js"
12416     */
12417    /**
12418    * Unlink google calendar 
12419    * @return mixed - bool json_encode
12420    */
12421    public function unlinkGoogleCalendar() {
12422        $this->autoRender = false;
12423        $response = array(
12424            'success' => false
12425        );
12426        require ROOT. "/teacher/Controller/GoogleCalendarSettingController.php";        
12427        $googleCalendarSetting     = new GoogleCalendarSettingController();
12428        $mobappTeacherId         = $this->Session->read('googleauthTeacherId');
12429        $teacherId                 = !empty($mobappTeacherId) ? $mobappTeacherId : $this->Auth->user('id');
12430        $response['success']     = $googleCalendarSetting->unlinkGoogleCalendar(array(
12431            'teacher_id' => $teacherId
12432        ));
12433        return json_encode($response); 
12434    }
12435
12436    // NJ-27262 - check if user is a chocotto user
12437    private function isUserChocottoCamp($membershipIndex) {
12438        return in_array($membershipIndex, [
12439            Configure::read('membership_type_chocotto_plan_free'), 
12440            Configure::read('membership_type_chocotto_plan_paid')
12441        ]);
12442    }
12443    
12444    /**
12445     * @api {get} /teacher/api/getUploadRecordedFileAsync getUploadRecordedFileAsync()
12446     * @apiName getUploadRecordedFileAsync
12447     * @apiGroup API
12448     * @apiDescription uploads an audio file, optionally stores it in S3, and returns the result.
12449     *
12450     * @apiBody {File} audio The audio file to be uploaded.
12451     * @apiBody {String} [connect_id] The connect ID.
12452     * @apiBody {Boolean} [store_audio_record] Indicates if the audio record should be stored.
12453     * @apiBody {Number} [user_id] The user ID.
12454     *
12455     * @apiSuccess {Boolean} success Indicates if the operation was successful.
12456     * @apiSuccess {String} [audio_url] The URL of the stored audio file.
12457     * @apiSuccess {String} [textbook_name] The name of the textbook.
12458     * 
12459     * @apiErrorExample {json} Success-Response:
12460     *     {
12461     *       "success": true,
12462     *       "audio_url": "bucket_name/file_name.wav",
12463     *       "textbook_name": "Textbook Category : Textbook Name"
12464     *     }
12465     *
12466     * @apiSampleRequest off
12467     */
12468    /**
12469    * Save audio 
12470    * @return json $response
12471  */
12472    public function getUploadRecordedFileAsync() {
12473        $this->autoRender = false;
12474        $response = array(
12475            'success' => false,
12476            'convertedSpeech' => null,
12477            'config' => array()
12478        );
12479
12480        $uploader_id = $this->Auth->user('id');
12481
12482        // check if request has a valid file
12483        if (!isset($_FILES['audio']['size']) || !$_FILES['audio']['size']) {
12484            return json_encode($response);
12485        }
12486
12487        $connectId                 = !empty($this->request->data['connect_id']) ? $this->request->data['connect_id'] : null;
12488        $storedRecordingFlag     = !empty($this->request->data['store_audio_record']) ? true : false;
12489        $userId                    = !empty($this->request->data['user_id']) ? $this->request->data['user_id'] : null;
12490
12491        // Get audio temporary folder
12492        $audioContainer = ROOT . '/instructor/webroot/sound';
12493        if (!is_dir($audioContainer)) {
12494            mkdir($audioContainer);
12495        }
12496
12497        // move the file from temp name to local folder using $output name
12498        $input = $_FILES['audio']['tmp_name'];
12499        $fileName = $uploader_id . $_FILES['audio']['name'] . '.wav';
12500        $output = $audioContainer . '/' . $fileName;
12501
12502        // delete audio file if already exist
12503        if (file_exists($output)) {
12504            unlink($output);
12505        }
12506        // upload local file
12507        $moveFileSatus = move_uploaded_file($input, $output);
12508
12509        //-- Move audio recordings to s3 
12510        $this->loadModel('FileStorage');
12511        if ( $moveFileSatus && !empty($storedRecordingFlag) ) {
12512            $filename     = time() . uniqid() . '_' . basename($output);
12513            $tempResult = $this->FileStorage->uploadFile(array(
12514                'uploader_id'     => $this->Auth->user('id'),
12515                'uploader_type' => 34,
12516                'source'         => $output,
12517                'key'             => $filename,
12518                'file'             => $_FILES['audio'],
12519                'delete_image'     => true
12520            ));
12521            $fileUrl = $tempResult['FileStorage']['url'];
12522
12523            $response['audio_url'] = str_replace('https://', '', $fileUrl);
12524            $response['textbook_name'] = '';            
12525            //-- user current language
12526            $userLanguage = $this->User->fetchUserCurrentLanguage(array(
12527                'user_id' => $userId
12528            ));
12529            $textbookName = $this->TextbookConnect->getTextbookName(array(
12530                'connect_id' => $connectId,
12531                'native_language' => $userLanguage,
12532                'translate_other_lang' => true
12533            ));
12534
12535            $response['textbook_name'] .= !empty($textbookName['textbook_category_name']) ? $textbookName['textbook_category_name'] : '';
12536            $response['textbook_name'] .= !empty($textbookName['textbook_subcategory_name']) ? ' ' . $textbookName['textbook_subcategory_name'] : '';
12537            $response['textbook_name'] .= !empty($textbookName['textbook_name']) ? ' : ' . $textbookName['textbook_name'] : '';
12538            $response['success'] = true;
12539        }
12540
12541        // - delete audio file if already exist
12542        if (file_exists($output)) {
12543            unlink($output);
12544        }
12545
12546        return json_encode($response);
12547  }
12548
12549    /**
12550     * @api {get} /teacher/api/googleConvertedFileUpload googleConvertedFileUpload()
12551     * @apiName googleConvertedFileUpload
12552     * @apiGroup API
12553     * @apiDescription uploads an audio file, converts it to text using Google Cloud Storage, and returns the result.
12554     *
12555     * @apiBody {File} audio The audio file to be uploaded.
12556     *
12557     * @apiSuccess {Boolean} success Indicates if the operation was successful.
12558     * @apiSuccess {String} gsurl The Google Cloud Storage URL of the uploaded file.
12559     * @apiSuccess {String} filename The name of the uploaded file.
12560     * @apiSuccess {String} output The output file path.
12561     * @apiSuccess {String} convertedSpeech The converted speech text.
12562     * 
12563     * @apiErrorExample {json} Success-Response:
12564     *     {
12565     *       "success": true,
12566     *       "gsurl": "https://storage.googleapis.com/bucket_name/file_name.wav",
12567     *       "filename": "12345audio_name-1.wav",
12568     *       "output": "/path/to/output/file.wav",
12569     *       "convertedSpeech": "Translated text"
12570     *     }
12571     *
12572     * @apiSampleRequest off
12573     */
12574    public function googleConvertedFileUpload() {
12575        $this->autoRender = false;
12576        $response = array(
12577            'success' => false,
12578        );
12579
12580        $uploader_id = $this->Auth->user('id');
12581
12582        // check if request has a valid file
12583        if (!isset($_FILES['audio']['size']) || !$_FILES['audio']['size']) {
12584            return json_encode($response);
12585        }
12586
12587        // Get audio temporary folder
12588        $audioContainer = ROOT . '/instructor/webroot/sound';
12589        if (!is_dir($audioContainer)) {
12590            mkdir($audioContainer);
12591        }
12592
12593        // move the file from temp name to local folder using $output name
12594        $input = $_FILES['audio']['tmp_name'];
12595        $fileName = $uploader_id . $_FILES['audio']['name'] . '-1.wav';
12596        $output = $audioContainer . '/' . $fileName;
12597
12598        // delete audio file if already exist
12599        if (file_exists($output)) {
12600            unlink($output);
12601        }
12602        // upload local file
12603        $moveFileSatus = move_uploaded_file($input, $output);
12604            // query speech to text
12605        if ($moveFileSatus) {
12606            //uploaded successfully
12607            if (!class_exists('GoogleCloudStorage')) {
12608                App::import('Vendor', 'GoogleCloudStorage');
12609            }
12610            $gsURL = GoogleCloudStorage::upload_object($fileName, $output);
12611            $response = GoogleCloudStorage::fetchConvertAudioToSpeechAsync($gsURL);
12612            $response['success'] = true;
12613            $response['gsurl'] = $gsURL;
12614            $response['filename'] = $fileName;
12615            $response['output'] = $output;
12616        }
12617        return json_encode($response);
12618    }
12619
12620    /**
12621     * @api {get} /teacher/api/googleConvertedSpeech googleConvertedSpeech()
12622     * @apiName googleConvertedSpeech
12623     * @apiGroup API
12624     * @apiDescription converts speech to text using Google Cloud Storage and logs the results.
12625     *
12626     * @apiBody {String} convert_name The name of the file to be converted.
12627     * @apiBody {String} filename The name of the file.
12628     * @apiBody {String} output The output file path.
12629     *
12630     * @apiSuccess {Boolean} success Indicates if the operation was successful.
12631     * @apiSuccess {String} convertedSpeech The converted speech text.
12632     * @apiSuccess {String} totalTime The total time of the speech.
12633     * 
12634     * @apiErrorExample {json} Success-Response:
12635     *     {
12636     *       "success": true,
12637     *       "convertedSpeech": "Translated text",
12638     *       "totalTime": "10s"
12639     *     }
12640     *
12641     * @apiSampleRequest off
12642     */
12643    public function googleConvertedSpeech() {
12644        $this->autoRender = false;
12645        $response = array(
12646            'success' => false
12647        );
12648        $name = $this->request->data['convert_name'];
12649        $fileName = $this->request->data['filename'];
12650        $output = $this->request->data['output'];
12651        if (!class_exists('GoogleCloudStorage')) {
12652            App::import('Vendor', 'GoogleCloudStorage');
12653        }
12654        $response = GoogleCloudStorage::uploadRecordedFileWtResponse($name);
12655        if (isset($response['convertedSpeech']) && $response['convertedSpeech'] == "") {
12656                $response['convertedSpeech'] = '{"jpn" : "〔エラー〕:音声認識に問題があります。ヘッドセットあるいはイヤホンの着用/インターネット環境のご確認をお願いします","eng" : "Error : There is a problem with voice recognition. Please wear a headset or earphones and check your internet environment"}';
12657                $response['success'] = true;
12658        }
12659        if (isset($response['totalTime']) && !empty($response['totalTime'])) {
12660            $sum = str_replace('s', '', $response['totalTime']);
12661            //save speech to text logs
12662            $logParams = array(
12663                'service' => 2,
12664                'type' => 3,
12665                'controller' => static::class,
12666                'method' => __METHOD__,
12667                'url' => $this->request->here(),
12668                'value'    => number_format($sum, 2 , '.', '')
12669            );
12670            $googleLogs = ClassRegistry::init('GoogleTranslateLog');
12671            $result = $googleLogs->saveLog($logParams);        
12672            //check if logs saved
12673            if(!$result){
12674                $this->log('error', __METHOD__." -- [Google Translate] Unable to save logs. ");
12675            }
12676            // delete from google buckets
12677            GoogleCloudStorage::delete_object($fileName);
12678            
12679            $this->log("RESULTTTT: " . json_encode($response), 'debug');
12680            // - delete audio file if already exist
12681            if (file_exists($output)) {
12682                unlink($output);
12683            }
12684            $response['success'] = true;
12685        }    
12686        return json_encode($response);
12687    }
12688
12689    /*
12690     * Chivox get label translations
12691     * used in stress and intonation chivox textbooks
12692     * @param language (string) - required
12693     * @return res array
12694     */ 
12695    private function getLabelTranslations($language = 'en') {
12696        $result = [];
12697        $labelTranslations = array(
12698            'Correct' => array(
12699                'en' => 'Correct',
12700                'ja' => '正解',
12701                'zh-tw' => '正確',
12702                'ko' => '정답',
12703                'pt-br' => 'Correto',
12704                'th' => 'คำตอบที่ถูกต้อง',
12705                'vi' => 'câu trả lời đúng',
12706                'zh-cn' => '正确答案',
12707            ),
12708            'You' => array(
12709                'en' => 'You',
12710                'ja' => 'あなた',
12711                'zh-tw' => '你',
12712                'ko' => '당신',
12713                'pt-br' => 'Você',
12714                'th' => 'คุณ',
12715                'vi' => 'Bạn',
12716                'zh-cn' => '你',
12717            )
12718        );
12719
12720        $result['Correct'] = $labelTranslations['Correct'][$language] ?? $labelTranslations['Correct']['en'];
12721        $result['You'] = $labelTranslations['You'][$language] ?? $labelTranslations['You']['en'];
12722        return $result;
12723    }
12724
12725    /**
12726     * NJ-57841: Sends a Slack message to notify about a stream check error for a student.
12727     */
12728    public function sendSlackStreamCheckError() {
12729        $this->autoRender = false;
12730        
12731        $data = $this->request->data;
12732        $chatHash = $data['chatHash'] ?? '';
12733        $errorList = $data['errorList'] ?? [];
12734
12735        if($this->request->is('post') && !empty($chatHash)) {
12736            // - set slack message
12737            $mySlack = new mySlack();
12738            $mySlack->token = "xoxb-392902820692-6499139810404-RHHGc6Z5ZB9o2KkiGhzTTtlQ";
12739            $mySlack->username = "Student Stream Check - Invalid";
12740            $mySlack->channel = myTools::checkChannel("#bad-teacher-connections", "#bad-teacher-connections-dev");
12741            $mySlack->text = "";
12742            $mySlack->text .= "```";
12743            $mySlack->text .= "chat_hash: " . $chatHash;
12744            $mySlack->text .= "\nTeacher side JS errors: " . json_encode($errorList, true);
12745            $mySlack->text .= "```";
12746            
12747            // - send slack message
12748            $mySlack->postMessage();
12749        }
12750    }
12751
12752    /**
12753     * NJ-64043: check if chat hash exists in lesson onair table 
12754     **/
12755    public function lessonChatHashChecker() {
12756        $this->autoRender = false;
12757        $data = $this->request->query;
12758        $chatHash = $data['chat_hash'] ?? '';
12759        $return = ['exist' => false];
12760
12761        if(empty($chatHash)) {
12762            $this->log("Invalid chat hash: " . $chatHash, 'debug');
12763            return json_encode($return);
12764        }
12765
12766        $this->LessonOnair->openDBReplica();
12767        $chatHash = $this->LessonOnair->find('count', array(
12768            'conditions' => array(
12769                'chat_hash' => $chatHash,
12770                'teacher_id' => $this->Auth->user('id')
12771            ),
12772        ));
12773        $this->LessonOnair->closeDBReplica();
12774
12775        if ($chatHash) {
12776            $return['exist'] = true;
12777        }
12778
12779        return json_encode($return);
12780    }
12781}